在Android開發中,我們經常會用到viewpager這種滑動的控件,來讓用戶有更好的體驗,一般的viewpager都會有一個綁定的頁面指示器,那麼這個頁面指示器是怎麼做的呢,方法比較簡單,我們可以自己定義一個view來實現它。
1、效果圖:
2、分析代碼:
自定義的IndicatorView.java:
/**
* @author xpd3581
* @date 2016-10-30
* @version 1.0
*/
package com.xingpidong.demo_viewpager_indicator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.example.demo_viewpager_indicator.R;
/**
* IndicatorView 2016-10-30
*/
public class IndicatorView extends View {
// 指示器圖標,這裏是一個 drawable,包含兩種狀態,
// 選中和非選中狀態
private Drawable mIndicator;
// 指示器圖標的大小,根據圖標的寬和高來確定,選取較大者
private int mIndicatorSize;
// 整個指示器控件的寬度
private int mWidth;
/* 圖標加空格在家 padding 的寬度 */
private int mContextWidth;
// 指示器圖標的個數,就是當前ViwPager 的 item 個數
private int mCount;
/* 每個指示器之間的間隔大小 */
private int mMargin;
/* 當前 view 的 item,主要作用,是用於判斷當前指示器的選中情況 */
private int mSelectItem;
/* 指示器根據ViewPager 滑動的偏移量 */
private float mOffset;
/* 指示器是否實時刷新 */
private boolean mSmooth;
public IndicatorView(Context context) {
this(context, null);
}
public IndicatorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 通過 TypedArray 獲取自定義屬性
TypedArray typedArray = getResources().obtainAttributes(attrs,
R.styleable.IndicatorView);
// 獲取自定義屬性的個數
int N = typedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.IndicatorView_indicator_icon:
// 通過自定義屬性拿到指示器
mIndicator = typedArray.getDrawable(attr);
break;
case R.styleable.IndicatorView_indicator_margin:
float defaultMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 5, getResources()
.getDisplayMetrics());
mMargin = (int) typedArray.getDimension(attr, defaultMargin);
break;
case R.styleable.IndicatorView_indicator_smooth:
mSmooth = typedArray.getBoolean(attr, false);
break;
}
}
// 使用完成之後記得回收
typedArray.recycle();
initIndicator();
}
private void initIndicator() {
// 獲取指示器的大小值。一般情況下是正方形的,也是時,你的美工手抖了一下,切出一個長方形來了,
// 不用怕,這裏做了處理不會變形的
mIndicatorSize = Math.max(mIndicator.getIntrinsicWidth(),
mIndicator.getIntrinsicHeight());
/* 設置指示器的邊框 */
mIndicator.setBounds(0, 0, mIndicator.getIntrinsicWidth(),
mIndicator.getIntrinsicWidth());
}
/**
* 測量View 的大小
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* 測量寬度,計算當前View 的寬度
*
* @param widthMeasureSpec
* @return
*/
private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int width;
int desired = getPaddingLeft() + getPaddingRight() + mIndicatorSize
* mCount + mMargin * (mCount - 1);
mContextWidth = desired;
if (mode == MeasureSpec.EXACTLY) {
width = Math.max(desired, size);
} else {
if (mode == MeasureSpec.AT_MOST) {
width = Math.min(desired, size);
} else {
width = desired;
}
}
mWidth = width;
return width;
}
private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int height;
if (mode == MeasureSpec.EXACTLY) {
height = size;
} else {
int desired = getPaddingTop() + getPaddingBottom() + mIndicatorSize;
if (mode == MeasureSpec.AT_MOST) {
height = Math.min(desired, size);
} else {
height = desired;
}
}
return height;
}
/**
* 繪製指示器
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
/*
* 首先得保存畫布的當前狀態,如果位置行這個方法 等一下的 restore()將會失效,canvas 不知道恢復到什麼狀態 所以這個
* save、restore 都是成對出現的,這樣就很好理解了。
*/
canvas.save();
/*
* 這裏開始就是計算需要繪製的位置, 如果不好理解,請按照我說的做,拿起 附近的紙和筆,在紙上繪製一下,然後 你就一目瞭然了,
*/
int left = mWidth / 2 - mContextWidth / 2 + getPaddingLeft();
canvas.translate(left, getPaddingTop());
for (int i = 0; i < mCount; i++) {
/*
* 這裏也需要解釋一下, 因爲我們額 drawable 是一個selector 文件 所以我們需要設置他的狀態,也就是 state
* 來獲取相應的圖片。 這裏是獲取未選中的圖片
*/
mIndicator.setState(EMPTY_STATE_SET);
/* 繪製 drawable */
mIndicator.draw(canvas);
/* 每繪製一個指示器,向右移動一次 */
canvas.translate(mIndicatorSize + mMargin, 0);
}
/*
* 恢復畫布的所有設置,也不是所有的啦, 根據 google 說法,就是matrix/clip 只能恢復到最後調用 save 方法的位置。
*/
canvas.restore();
/* 這裏又開始計算繪製的位置了 */
float leftDraw = (mIndicatorSize + mMargin) * (mSelectItem + mOffset);
/*
* 計算完了,又來了,平移,爲什麼要平移兩次呢? 也是爲了好理解。
*/
canvas.translate(left, getPaddingTop());
canvas.translate(leftDraw, 0);
/*
* 把Drawable 的狀態設爲已選中狀態 這樣獲取到的Drawable 就是已選中 的那張圖片。
*/
mIndicator.setState(SELECTED_STATE_SET);
/* 這裏又開始繪圖了 */
mIndicator.draw(canvas);
}
/**
* 此ViewPager 一定是先設置了Adapter, 並且Adapter 需要所有數據,後續還不能 修改數據
*
* @param viewPager
*/
public void setViewPager(ViewPager viewPager,
OnPageChangeListener mPageChangeListener) {
if (viewPager == null) {
return;
}
PagerAdapter pagerAdapter = (PagerAdapter) viewPager.getAdapter();
if (pagerAdapter == null) {
throw new RuntimeException("pagerAdapter = null");
}
mCount = pagerAdapter.getCount();
viewPager.setOnPageChangeListener(mPageChangeListener);
mSelectItem = viewPager.getCurrentItem();
invalidate();
}
public Drawable getmIndicator() {
return mIndicator;
}
public void setmIndicator(Drawable mIndicator) {
this.mIndicator = mIndicator;
}
public int getmIndicatorSize() {
return mIndicatorSize;
}
public void setmIndicatorSize(int mIndicatorSize) {
this.mIndicatorSize = mIndicatorSize;
}
public int getmWidth() {
return mWidth;
}
public void setmWidth(int mWidth) {
this.mWidth = mWidth;
}
public int getmContextWidth() {
return mContextWidth;
}
public void setmContextWidth(int mContextWidth) {
this.mContextWidth = mContextWidth;
}
public int getmCount() {
return mCount;
}
public void setmCount(int mCount) {
this.mCount = mCount;
}
public int getmMargin() {
return mMargin;
}
public void setmMargin(int mMargin) {
this.mMargin = mMargin;
}
public int getmSelectItem() {
return mSelectItem;
}
public void setmSelectItem(int mSelectItem) {
this.mSelectItem = mSelectItem;
}
public float getmOffset() {
return mOffset;
}
public void setmOffset(float mOffset) {
this.mOffset = mOffset;
}
public boolean ismSmooth() {
return mSmooth;
}
public void setmSmooth(boolean mSmooth) {
this.mSmooth = mSmooth;
}
}
講一下
public IndicatorView(Context context, AttributeSet attrs, int
defStyleAttr)
這個構造函數,如果你想自定義屬性,那麼必須通過將xml文件和你的view綁定到一起,這個構造函數就是做的這樣的事。
它是怎麼做的呢,來看一下:
getResources().obtainAttributes(attrs,R.styleable.IndicatorView)
這句話就引用了自定義屬性集IndicatorView
,那麼它是怎麼寫的,存放在哪裏?
看圖:
在res/values文件夾下面新建一個attrs的資源文件,Android編譯器就可以根據attrs找到你的自定義屬性,並用Xml的pullParser解析到java代碼中從而供我們使用。
<attr name="indicator_icon" format="integer" />
<attr name="indicator_margin" format="dimension" />
<attr name="indicator_smooth" format="boolean" />
第一行的
icon
的format
類型,指定的是integer
類型,就是你項目當中R.java
中的int
值,這裏定義icon
是圖標,也就可以爲它指定一個drawable
圖片,可以是一張真實的圖片,也可以是自己定義的shape
的圖形。第二行是
dimension
,就是間距、距離、尺寸的意思,他會去調用res/values
下的dimens.xml
文件,你可以在那裏寫好尺寸來調用。第三行是
boolean
,這個就不說了,就像開關鍵一樣。
現在,我們的自定義IndicatorView和自定義屬性都寫完了,那麼接下來就是去調用了:
來看activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:customAttr="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#8888"
tools:context="com.xingpidong.demo_viewpager_indicator.MainActivity" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="50dp"
android:background="#ff0f"
android:paddingBottom="20dp" >
<android.support.v4.view.ViewPager
android:id="@+id/mViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.xingpidong.demo_viewpager_indicator.IndicatorView
android:id="@+id/mIndicatorView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
customAttr:indicator_icon="@drawable/indicator_selector"
customAttr:indicator_margin="3dp"
customAttr:indicator_smooth="true" />
<com.xingpidong.demo_viewpager_indicator.IndicatorView
android:id="@+id/mIndicatorView3"
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
customAttr:indicator_icon="@drawable/my_indicator_selector"
customAttr:indicator_margin="3dp"
customAttr:indicator_smooth="true" />
</RelativeLayout>
<com.xingpidong.demo_viewpager_indicator.IndicatorView
android:id="@+id/mIndicatorView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
customAttr:indicator_icon="@drawable/indicator_selector"
customAttr:indicator_margin="8dp"
customAttr:indicator_smooth="false" />
</RelativeLayout>
其中的根佈局中有一句話:
xmlns:customAttr="http://schemas.android.com/apk/res-auto"
customAttr
是自己定義的,你寫什麼名字都可以,這個名字就是你對控件添加屬性的前綴標籤,就像 android:focusable="true"
當中的android 標籤一樣。
添加自定義屬性:
customAttr:indicator_icon="@drawable/indicator_selector"
customAttr:indicator_margin="3dp"
customAttr:indicator_smooth="true"
使用起來就這麼簡單。
再看主界面的代碼MainActivity.java:
package com.xingpidong.demo_viewpager_indicator;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.demo_viewpager_indicator.R;
/**
* xpd 2016-11-3
*/
public class MainActivity extends Activity {
private ViewPager mViewPager;
private MyAdapter mPagerAdapter;
private OnPageChangeListener mOnPageChangeListener;
private IndicatorView mIndicatorView1;
private IndicatorView mIndicatorView2;
private IndicatorView mIndicatorView3;
private List<View> mList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) this.findViewById(R.id.mViewPager);
mIndicatorView1 = (IndicatorView) this
.findViewById(R.id.mIndicatorView1);
mIndicatorView2 = (IndicatorView) this
.findViewById(R.id.mIndicatorView2);
mIndicatorView3 = (IndicatorView) this
.findViewById(R.id.mIndicatorView3);
setUpPageChangeListener();
setDatas();
setUpViewPager();
}
/**
* new 一個pagechangelistener
*/
public void setUpPageChangeListener() {
mOnPageChangeListener = new OnPageChangeListener() {
@Override
public void onPageSelected(int pageIndex) {
mIndicatorView1.setmSelectItem(pageIndex);
mIndicatorView1.invalidate();
mIndicatorView2.setmSelectItem(pageIndex);
mIndicatorView2.invalidate();
mIndicatorView3.setmSelectItem(pageIndex);
mIndicatorView3.invalidate();
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
if (mIndicatorView1.ismSmooth()) {
mIndicatorView1.setmSelectItem(position);
mIndicatorView1.setmOffset(positionOffset);
mIndicatorView1.invalidate();
}
if (mIndicatorView2.ismSmooth()) {
mIndicatorView2.setmSelectItem(position);
mIndicatorView2.setmOffset(positionOffset);
mIndicatorView2.invalidate();
}
if (mIndicatorView3.ismSmooth()) {
mIndicatorView3.setmSelectItem(position);
mIndicatorView3.setmOffset(positionOffset);
mIndicatorView3.invalidate();
}
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
};
}
/**
* 初始化數據集合
*/
private void setDatas() {
mList = new ArrayList<View>();
for (int i = 0; i < 5; i++) {
TextView view = new TextView(this);
view.setText("這是第 " + (i + 1) + " 頁!");
view.setGravity(Gravity.CENTER);
mList.add(view);
}
}
/**
* viewpager設置adapter,和頁面指示器
*/
private void setUpViewPager() {
mPagerAdapter = new MyAdapter(mList);
mViewPager.setAdapter(mPagerAdapter);
mIndicatorView1.setViewPager(mViewPager, mOnPageChangeListener);
mIndicatorView2.setViewPager(mViewPager, mOnPageChangeListener);
mIndicatorView3.setViewPager(mViewPager, mOnPageChangeListener);
}
class MyAdapter extends PagerAdapter {
List<View> mDatas;
public MyAdapter(List<View> list) {
this.mDatas = list;
}
@Override
public int getCount() {
return null == mDatas ? 0 : mDatas.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mDatas.get(position));
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(mDatas.get(position));
return mDatas.get(position);
}
}
}
indicator_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_selected="true" android:drawable="@drawable/white_indicator"></item>
<item android:state_selected="false" android:drawable="@drawable/gray_indicator"></item>
</selector>
my_indicator_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_selected="true" android:drawable="@drawable/my_selected_circle"></item>
<item android:state_selected="false" android:drawable="@drawable/my_unselected_circle"></item>
</selector>
my_selected_circle.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#fff0" />
<corners android:radius="5dp" />
<size android:width="10dp" android:height="10dp"/>
<stroke android:color="#fef2"/>
</shape>
my_unselected_circle.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#000" />
<corners android:radius="5dp" />
<size android:width="10dp" android:height="10dp"/>
</shape>
剩下的兩個,white_indicator
和gray_indicator
就是兩張小圖標了,是美工妹妹提供的,我們也可以自己用ps簡單的製作一下。
好了,講到這裏,我們的ViewPager頁面指示器就講完了,如果你覺得本文對你有幫助,不要忘了爲博主點贊哦。(^__^) 嘻嘻……