日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

android 指示器平移动画,Android实现带指示器的自动轮播式ViewPager

發布時間:2023/12/18 Android 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 指示器平移动画,Android实现带指示器的自动轮播式ViewPager 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

最近在做項目的時候,有個需求就是實現自動輪播式的ViewPager,最直觀的例子就是知乎日報頂部的ViewPager,它內部有著好幾個子view,每個一段時間便自動滑動到下一個item view,而底部的指示器也隨之跟著改變。使用這種ViewPager的好處是在有限的空間內可以展示出多樣化的信息。輪播式ViewPager廣泛應用于各種應用內部,用于展示廣告等。抱著學習和分享的目的,筆者把輪播式ViewPager寫成了一個獨立的控件,以方便以后的使用。

效果展示

話不多說,我們先來看看實現的效果是怎樣的:

從上面的動態圖可以看到,當我們手指拖動ViewPager的時候,下方的指示器隨著頁面的滑動而滑動,當點擊添加數據的按鈕的時候,ViewPager的數據項變多,同時下方的指示器也隨之改變,適應了數據項的數目。

從上面的動態圖可以看到,當我們不用手指進行拖動的時候,該ViewPager會每隔4s左右的時間自動進行滾動,滾動到最后一個item view的時候,下一次會滾到第一個位置。

GitHub地址及使用介紹

讀者可以直接到我的GitHub中獲取源碼。

GitHub:BannerViewPager,控件及其相關文件都放在了該目錄下的library模塊內,而app模塊則是上面效果展示的一個簡單應用。

通過以下幾個步驟,就能方便地使用該控件了:

1、像普通的ViewPager一樣,在布局文件中放入該控件如下:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/banner"

android:layout_width="match_parent"

android:layout_height="200dp">

2、獲取BannerViewPager的實例,進行相應的配置,比如我們使用ViewPager的時候,也需要設置它的適配器等。這里筆者實現了一個ViewPagerAdapter,用作BannerViewPager的適配器:

//獲取BannerViewPager實例

bannerViewPager = (BannerViewPager) findViewById(R.id.banner);

//實例化ViewPagerAdapter,第一個參數是View集合,第二個參數是頁面點擊監聽器

mAdapter = new ViewPagerAdapter(mViews, new OnPageClickListener() {

@Override

public void onPageClick(View view, int position) {

Log.d("cylog","position:"+position);

}

});

//設置適配器

bannerViewPager.setAdapter(mAdapter);

和一般的ViewPager沒什么兩樣,都是:獲取實例——創建適配器——設置適配器。而適配器的數據集一般都是一個View集合,用作ViewPager的item view,所以需要事先準備好相應的View集合。此外,一般輪播式ViewPager點擊某一項后會打開相應的頁面,所以這里提供了一個OnPageClickListener的監聽器,在創建適配器的時候同時創建該監聽器即可。

原理簡析

接下來,筆者將簡要分析BannerViewPager的實現思路,具體的請讀者參考源碼~

實現自動滾動

首先,我們先思考一下,系統自帶的ViewPager是一個獨立控件,沒有指示器,也沒有自動滾動的功能,但是它是一個現成的,可左右滑動的控件,我們肯定是需要ViewPager的,因此,我們可以利用一個布局,把ViewPager包裹起來,同時在這個布局里面再放入indicator(指示器)。

那么,第一步,先新建BannerViewPager.java繼承自FrameLayout,而這個FrameLayout有兩個子元素:ViewPager和indicator。至于indicator,下面會說到。在構造函數內對這兩個控件進行初始化先:

public class BannerViewPager extends FrameLayout implements ViewPager.OnPageChangeListener {

private ViewPager mViewPager;

private ViewPagerIndicator mIndicator;

private ViewPagerAdapter mAdapter;

//...

public BannerViewPager(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

this.mContext = context;

initViews();

}

private void initViews() {

//initialize the viewpager

mViewPager = new ViewPager(mContext);

ViewPager.LayoutParams lp = new ViewPager.LayoutParams();

lp.width = ViewPager.LayoutParams.MATCH_PARENT;

lp.height = ViewPager.LayoutParams.MATCH_PARENT;

mViewPager.setLayoutParams(lp);

//initialize the indicator

mIndicator = new ViewPagerIndicator(mContext);

FrameLayout.LayoutParams indicatorlp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);

indicatorlp.gravity = Gravity.BOTTOM | Gravity.CENTER;

indicatorlp.bottomMargin = 20;

mIndicator.setLayoutParams(indicatorlp);

}

//省略...

}

這里沒什么好說的,主要是對ViewPager和ViewPagerIndicator進行初始化,設置它們的布局參數以便在FrameLayout中得到正確的顯示。

接著,對ViewPager實現自動滾動,這個的實現原理也不難,我們只要知道每時每刻的ViewPager的滑動狀態、當前的page position值即可,而ViewPager有這樣一個監聽器:ViewPager.OnPageChangeListener,只要ViewPager進行了滑動,就會回調這個監聽器的如下幾個方法:

public interface OnPageChangeListener {

//只要ViewPager進行了滑動,該方法就會回調

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

//當前頁面被選定的時候,回調

public void onPageSelected(int position);

//ViewPager的狀態發生改變的時候,回調

public void onPageScrollStateChanged(int state);

}

那么,我們為ViewPager設置監聽器(調用addOnPageChangeListener方法),并且重寫這幾個方法以實現我們的需求:

//保存當前的position值

private int mCurrentPosition;

//viewpager's rolling state

private int mViewPagerScrollState;

@Override

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

setIndicator(position,positionOffset); //下面會講到

}

@Override

public void onPageSelected(int position) {

mCurrentPosition = position;

}

@Override

public void onPageScrollStateChanged(int state) {

if(state == ViewPager.SCROLL_STATE_DRAGGING){

mViewPagerScrollState = ViewPager.SCROLL_STATE_DRAGGING;

}else if(state == ViewPager.SCROLL_STATE_IDLE){

mReleasingTime = (int) System.currentTimeMillis();

mViewPagerScrollState = ViewPager.SCROLL_STATE_IDLE;

}

}

每當當前頁面被選中的時候,就會調用onPageSelected方法,此時保存當前position值。那么,什么叫做當前頁面被選中呢?經過實驗驗證,當一個Item被完全展示在ViewPager中的時候,就是選中狀態,但如果當前正在被手指拖動,即使下一個item滑動到了中間位置,也不是選中狀態。接著,我們看onPageScrollStateChanged方法,當ViewPager的狀態發生改變的時候,就會觸發。那么,**ViewPager的狀態改變是什么意思呢?**ViewPager有如下三種狀態:IDLE,停止狀態,無手指觸摸;DRAGGING,正在被手指拖動;SETTLING,松開手指的時候,ViewPager由于慣性向能滑到的最后一個位置滑去的狀態。我們重寫的方法中,mViewPageSrollState記錄了ViewPager的實時狀態,同時停止狀態的時候,也記錄了一個mReleasingTime值,這個值的作用下面會介紹。通過這個監聽器,我們獲取到了mCurrentPosition和mViewPageScrollState這兩個值。

接下來,我們要考慮自動任務的問題了。在Android中,自動任務可以使用Handler和Runnable來實現,通過postDelay方法來不斷實現循環,代碼如下:

private Handler mHandler = new Handler(){

@Override

public void handleMessage(Message msg) {

switch (msg.what){

case MESSAGE_AUTO_ROLLING:

if(mCurrentPosition == mAdapter.getCount() - 1){

mViewPager.setCurrentItem(0,true);

}else {

mViewPager.setCurrentItem(mCurrentPosition + 1,true);

}

postDelayed(mAutoRollingTask,mAutoRollingTime);

break;

case MESSAGE_AUTO_ROLLING_CANCEL:

postDelayed(mAutoRollingTask,mAutoRollingTime);

break;

}

}

};

/**

* This runnable decides the viewpager should roll to next page or wait.

*/

private Runnable mAutoRollingTask = new Runnable() {

@Override

public void run() {

int now = (int) System.currentTimeMillis();

int timediff = mAutoRollingTime;

if(mReleasingTime != 0){

timediff = now - mReleasingTime;

}

if(mViewPagerScrollState == ViewPager.SCROLL_STATE_IDLE){

//if user's finger just left the screen,we should wait for a while.

if(timediff >= mAutoRollingTime * 0.8){

mHandler.sendEmptyMessage(MESSAGE_AUTO_ROLLING);

}else {

mHandler.sendEmptyMessage(MESSAGE_AUTO_ROLLING_CANCEL);

}

}else if(mViewPagerScrollState == ViewPager.SCROLL_STATE_DRAGGING){

mHandler.sendEmptyMessage(MESSAGE_AUTO_ROLLING_CANCEL);

}

}

};

在mAutoRollingTask這個Runnable內,我們根據不同的mViewPagerScrollState來決定是讓ViewPager滾動到下一個page還是等待,因為如果用戶當前正在觸摸ViewPage,那么肯定是不能自動滾動到下一頁的,此外,還有一種情況,就是當用戶手指離開屏幕的時候,需要等待一段時間才能開始自動滾動任務,否則會造成不好的用戶體驗,這也就是mReleasingTime的作用之處了。在Handler中,根據Runnable發送過來的不同信息來進行不同的操作,如果需要滾動到下一個頁面,則調用ViewPager#setCurrentItem方法來進行滑動,該方法有兩個參數,第一個參數是要滑動的位置,第二個參數表示是否開啟動畫。

實現指示器

接下來,我們來考慮,指示器怎么實現。指示器有如下需求:指示器由一系列圓點構成,未被選中的Page所對應的圓點為灰色,而選中的Page所對應的圓點為橙色,橙色的圓點能隨著Page的滑動而滑動。當ViewPage的數據變動的時候,比如新增了頁面,那么指示器所包含的圓點也會隨著變多。

那么,我們可以這樣來實現需求:灰色的圓點作為Indicator的背景,通過onDraw()方法來繪制,而橙色圓點則通過一個子View來顯示,利用onLayout()方法來控制它的位置,這樣就能實現橙色圓點在灰色圓點上運動的效果了。而它們具體的位置控制,可以利用上面ViewPager.OnPageChangeListener#onPageScrolled方法來獲取具體的位置以及位置偏移百分比。

我們先來實現繪制部分,新建ViewPagerIndicator.java繼承自LinearLayout,先對屬性初始化:

public class ViewPagerIndicator extends LinearLayout {

private Context mContext;

private Paint mPaint;

private View mMoveView;

//省略...

public ViewPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

this.mContext = context;

init();

}

private void init() {

//setOrientation(LinearLayout.HORIZONTAL);

setWillNotDraw(false);

mPaint = new Paint();

mPaint.setAntiAlias(true);

mPaint.setColor(Color.GRAY);

mMoveView = new MoveView(mContext);

addView(mMoveView);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

setMeasuredDimension(mPadding + (mRadius*2 + mPadding) * mItemCount,2*mRadius + 2*mPadding);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

for(int i = 0;i < mItemCount;i++){

canvas.drawCircle(mRadius + mPadding + mRadius * i *2 + mPadding * i,

mRadius + mPadding,mRadius,mPaint);

}

}

//省略...

private class MoveView extends View {

private Paint mPaint;

public MoveView(Context context) {

super(context);

mPaint = new Paint();

mPaint.setAntiAlias(true);

mPaint.setColor(Color.argb(255,255,176,93));

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

setMeasuredDimension(mRadius*2,mRadius*2);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);

}

}

}

從上面的代碼可以看到,在init()方法內,我們調用了setWillNotDraw(false)方法,這個方法有什么用呢?如果有寫過自定義View的讀者應該知道,ViewGroup默認是不會調用它自身的onDraw()方法的,只有調用了該方法設置為false或者給ViewGroup設置一種背景顏色的情況下才會調用onDraw()方法。

解決了這個問題后,我們來看onMeasure()方法,在這個方法內,我們要對該indicator的寬高做出測量,以便接下來的布局和繪制流程,而對于我們的需求而言,只要該布局能夠包裹住我們的指示器,并且四邊留有一定的空間即可,那么布局的寬度就與Page的數量有關了。為了方便起見,這里先給一個默認值,比如5個Page,那么對應5個灰色的圓點。

我們接著看onDraw()方法,這個方法內部,根據mItemCount的數量,來進行繪制圓形,這里沒什么好講的,只要注意他們之間的距離就可以了。

接著,我們來繪制橙色的圓點,新建一個內部類,繼承自View,同樣通過onMeasure、onDraw方法來進行測量、繪制流程,只不過顏色變了而已。

好了,繪制部分就完成了,接下來就是讓這個MoveView進行移動了,由于要使MoveView配合Page的滑動而滑動,我們需要Page的具體位置以及位置偏移量,而這兩個數值是在BannerViewPager的內部中獲得的,所以我們可以在BannerViewPager中,每一次調用onPageScrolled方法的時候,來調用我們的ViewPagerIndicator的一個方法,而在這個方法內部,來請求布局,這樣就能實現MoveView隨著Page的滑動而滑動的效果了,具體如下:

public class ViewPagerIndicator extends LinearLayout {

//以上省略..

public void setPositionAndOffset(int position,float offset){

this.mCurrentPosition = position;

this.mPositionOffset =offset;

requestLayout();

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

mMoveView.layout(

(int) (mPadding + mDistanceBtwItem * (mCurrentPosition + mPositionOffset) ),

mPadding,

(int) (mDistanceBtwItem * ( 1 + mCurrentPosition + mPositionOffset) ),

mPadding+mRadius*2);

}

}

在setPositionAndOffset方法內調用了requestLayout()方法,這個方法會導致View樹的測量、布局、重繪流程的發生,因此在onLayout方法內,通過mCurrentPosition、mPositionOffset這兩個值來控制MoveView的位置就可以了。

好了,到現在為止,ViewPagerIndicator基本已經完成了,但是還有一個問題,如果適配器里面的數據刷新了,page的數量變多了,而指示器的數目卻依然沒變,上面我們使用的mItemCount是默認值,為5個。因此,我們必須在數據刷新的時候,及時通知Indicator來增加指示器的數目。但是,我們進一步想想,數據列表保存在Adapter中,如果ViewPagerIndicator想要獲取數據,那就要得到Adapter的一個引用,或者說Adapter需要得到ViewPagerIndicator的引用以便能夠通知它,如果這樣做的話,相當于把兩個相關性不大的類聯系到了一起,耦合度過高,這樣不利于以后的維護。

因此,這里筆者采用了觀察者模式來實現Adapter數據刷新時通知ViewPagerIndicator的這樣一個需求。先新建兩個接口,一個是DataSetSubscriber,觀察者;另一個是DataSetSubject,被觀察者。

public interface DataSetSubscriber {

void update(int count);

}

public interface DataSetSubject {

void registerSubscriber(DataSetSubscriber subscriber);

void removeSubscriber(DataSetSubscriber subscriber);

void notifySubscriber();

}

這里實現思路是這樣的:在BannerViewPager內實現一個DataSetSubscriber(觀察者),在ViewPageAdapter內實現DataSetSubject(被觀察者),通過registerSubscriber方法進行注冊,當ViewPageAdapter的數據列表發生變動的時候,回調DataSetSubscriber的update()方法,并把當前的數據長度作為參數傳遞進來,而BannerViewPager再進一步調用ViewPagerIndicator的方法來重新布局即可。

先來看ViewPagerIndicator.java:

public class ViewPagerAdapter extends PagerAdapter implements DataSetSubject {

private List mSubscribers = new ArrayList<>();

private List extends View> mDataViews;

private OnPageClickListener mOnPageClickListener;

/**

* 構造函數

* @param mDataViews view列表

*/

public ViewPagerAdapter(List extends View> mDataViews,OnPageClickListener listener) {

this.mDataViews = mDataViews;

this.mOnPageClickListener = listener;

}

//省略...

@Override

public void notifyDataSetChanged() {

super.notifyDataSetChanged();

notifySubscriber();

}

@Override

public void registerSubscriber(DataSetSubscriber subscriber) {

mSubscribers.add(subscriber);

}

@Override

public void removeSubscriber(DataSetSubscriber subscriber) {

mSubscribers.remove(subscriber);

}

@Override

public void notifySubscriber() {

for(DataSetSubscriber subscriber : mSubscribers){

subscriber.update(getCount());

}

}

}```

由于數據列表的變動一般都會調用notifyDataSetChanged()方法,所以我們在這個方法內再調用notifySubscriber()方法即可。而在BannerViewPager,則實現DataSetSubscriber的update()方法即可,如下所示:

```java

public void setAdapter(ViewPagerAdapter adapter){

mViewPager.setAdapter(adapter);

mViewPager.addOnPageChangeListener(this);

mAdapter = adapter;

mAdapter.registerSubscriber(new DataSetSubscriber() {

@Override

public void update(int count) {

mIndicator.setItemCount(count);

}

});

//add the viewpager and the indicator to the container.

addView(mViewPager);

addView(mIndicator);

//start the auto-rolling task if needed

if(isAutoRolling){

postDelayed(mAutoRollingTask,mAutoRollingTime);

}

}

在update()方法內,調用了ViewPagerIndicator#setItemCount方法,從而重新布局。

那么,指示器也實現完畢了。

實現Page的點擊事件處理

還有最后一個需求,就是對Page的點擊進行處理,因為往往ViewPager的內容只是一個概括性的內容,為了得到更加詳細的信息,用戶通常會點擊它的item從而打開一個新的頁面,這樣就需要我們對點擊事件進行處理了。其實實現方式不難,思路類似于筆者之前在RecyclerView的相關文章的處理點擊事件中的方式,通過定義一個新的接口:OnPageClickListener,定義一個onPageClick方法。如下:

public interface OnPageClickListener {

void onPageClick(View view,int position);

}

只要在item view初始化的時候,給每個item view都設置一個View.OnClickListener,并且在onClick方法里面調用我們的onPageClick方法即可。

具體如下所示,ViewPagerAdapter:

@Override

public View instantiateItem(ViewGroup container, int position) {

View view = mDataViews.get(position);

final int i = position;

if(mOnPageClickListener != null){

view.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

mOnPageClickListener.onPageClick(v,i);

}

});

}

container.addView(view);

return view;

}

在構建適配器的時候,同時實現OnPageClickListener即可。

以上便是本文的全部內容,非常感謝你的閱讀~

歡迎到GitHub中獲取本文的源碼,歡迎star or fork。再次感謝!

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

總結

以上是生活随笔為你收集整理的android 指示器平移动画,Android实现带指示器的自动轮播式ViewPager的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。