自定义Indicator控件设置XML属性介绍:
Android Viewpager Indicator是android开发中最常用的控件之一,几乎所有的新闻类APP中都有使用,下面介绍其基本使用方法。
1. 首先一个indicator必须要与一个ViewPager关联在一起,所以它提供了一个setViewPager方法。
2 .它扩展了ViewPager.OnPageChangeListener接口,表示接管了ViewPager的Pager改变时的监听处理,这也是为什么为ViewPager设置OnPageChangeListener监听器时不能设置在ViewPager上而必须设置在indicator上的原因。
最终实现效果:
Java代码:
实现步骤:
首先我们创建一个集合,为了装ViewPager加载的图片控件,在定义一个Indicator的成员变量.
创建一个Handler设置为全局,通过Handler实现图片无限轮播,下面我会给大家详细的说明图片无限轮播的实现方法
- //创建一个集合装Viewpager加载的图片控件
- private List<View> mViews = new ArrayList<View>();
- private Indicator mIn;
- private Handler mHandler = new Handler();
- private ViewPager mViewPager;
//创建一个集合装Viewpager加载的图片控件
private List<View> mViews = new ArrayList<View>();
private Indicator mIn;
private Handler mHandler = new Handler();
private ViewPager mViewPager;
然后在
onCreate()里实现:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //初始化viewpager的item数据,往集合里面放数据,方便ViewPager拿数据
- initData();
- //初始化控件
- ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
- //初始化我的自定义控件
- mIn = (Indicator) findViewById(R.id.indicator);
- //设置ViewPager的监听器
- viewPager.setOnPageChangeListener(new MyPagerListner());
- //设置ViewPager适配器
- viewPager.setAdapter(new MyPagerAdapter());
- }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化viewpager的item数据,往集合里面放数据,方便ViewPager拿数据
initData();
//初始化控件
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
//初始化我的自定义控件
mIn = (Indicator) findViewById(R.id.indicator);
//设置ViewPager的监听器
viewPager.setOnPageChangeListener(new MyPagerListner());
//设置ViewPager适配器
viewPager.setAdapter(new MyPagerAdapter());
}
然后初始化ViewPager的
item数据,最后别忘记调用initData方法
- private void initData() {
- for (int x = 0; x < 4; x++) {
- //通过布局填充器,把一个布局XML文件转换为View对象
- View inflate = getLayoutInflater().inflate(R.layout.pager_item, null);
- //找到布局View里的ImageView控件对象
- ImageView imageview = (ImageView) inflate.findViewById(R.id.iv);
- //往ImageView控件里放图片
- imageview.setImageResource(R.mipmap.ic_launcher);
- //最后把布局View控件放到集合里
- mViews.add(inflate);
- }
- }
private void initData() {
for (int x = 0; x < 4; x++) {
//通过布局填充器,把一个布局XML文件转换为View对象
View inflate = getLayoutInflater().inflate(R.layout.pager_item, null);
//找到布局View里的ImageView控件对象
ImageView imageview = (ImageView) inflate.findViewById(R.id.iv);
//往ImageView控件里放图片
imageview.setImageResource(R.mipmap.ic_launcher);
//最后把布局View控件放到集合里
mViews.add(inflate);
}
}
创建ViewPager适配器,重写4个方法,下面给大家介绍这4个方法的作用.
1:getCount()是设置ViewPager的item数量
2:isViewFromObject()固定的格式
3:destroyItem()防止内存溢出,溢出ImageView对象
4:instantiateItem()此方法类似于ListView中的getView,第一个参数是ViewPager的化身,第二个参数是item的位置
- class MyPagerAdapter extends PagerAdapter {
- @Override
- public int getCount() {
- return Integer.MAX_VALUE;
- }
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- position %= 4;//防止角标越界
- container.removeView(mViews.get(position));
- }
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- position %= 4;//防止角标越界
- //从集合里拿对应位置的图片
- View view = mViews.get(position);
- //把ImageView的对象添加到Viewpager
- container.addView(view);
- //返回View对象
- return view;
- }
- }
class MyPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
position %= 4;//防止角标越界
container.removeView(mViews.get(position));
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
position %= 4;//防止角标越界
//从集合里拿对应位置的图片
View view = mViews.get(position);
//把ImageView的对象添加到Viewpager
container.addView(view);
//返回View对象
return view;
}
}
创建ViewPager的监听事件:
1:onPageScrolled()是滑动时调用
2:onPageScrollStateChanged()是选中时调用
3:onPageScrollStateChanged()是滑动状态改变时回调的方法
- class MyPagerListner implements ViewPager.OnPageChangeListener {
- /**
- * @param position item位置
- * @param positionOffset //偏移的百分比这个百分比永远接近于1
- * @param positionOffsetPixels //偏移量
- */
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- Log.d("DS", "onPageScrolled回调");
- Log.i("ds", "position :" + position + " positionOffset :" + positionOffset + " positionOffsetPixels : " + positionOffsetPixels);
- //往自定义控件里放入item位置及偏移的百分比,是小点可以动态的跟着ViewPager滑动
- mIn.setoffest(position, positionOffset);
- }
- @Override
- public void onPageSelected(int position) {
- Log.d("DS", "onPageScrolled 回调");
- }
- @Override
- public void onPageScrollStateChanged(int state) {
- Log.d("DS", "onPageScrollStateChanged 回调 hhh");
- }
- }
class MyPagerListner implements ViewPager.OnPageChangeListener {
/**
* @param position item位置
* @param positionOffset //偏移的百分比这个百分比永远接近于1
* @param positionOffsetPixels //偏移量
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.d("DS", "onPageScrolled回调");
Log.i("ds", "position :" + position + " positionOffset :" + positionOffset + " positionOffsetPixels : " + positionOffsetPixels);
//往自定义控件里放入item位置及偏移的百分比,是小点可以动态的跟着ViewPager滑动
mIn.setoffest(position, positionOffset);
}
@Override
public void onPageSelected(int position) {
Log.d("DS", "onPageScrolled 回调");
}
@Override
public void onPageScrollStateChanged(int state) {
Log.d("DS", "onPageScrollStateChanged 回调 hhh");
}
}
在这里给大家呈现关于实现ViewPager图片自动轮播
- private void autoScroll() {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- //获取当前的轮播的位置
- int currentItem = mViewPager.getCurrentItem();
- //从当前的图切换到另一张通过(currentItem + 1)就可以实现
- mViewPager.setCurrentItem(currentItem + 1);
- //通过mHandler请求延迟2秒
- mHandler.postDelayed(this, 2000);
- //调用触摸滑动事件方法
- onTouch();
- }
- }, 2000);
- }
private void autoScroll() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//获取当前的轮播的位置
int currentItem = mViewPager.getCurrentItem();
//从当前的图切换到另一张通过(currentItem + 1)就可以实现
mViewPager.setCurrentItem(currentItem + 1);
//通过mHandler请求延迟2秒
mHandler.postDelayed(this, 2000);
//调用触摸滑动事件方法
onTouch();
}
}, 2000);
}
关于实现触摸滑动的监听事件,它的作用是当你用手触摸ViewPager时它会监听你的手势,当你触碰到时就暂停轮播.
- private void onTouch() {
- //通过mViewPager去设置触摸滑动的点击事件
- mViewPager.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE:
- mHandler.removeMessages(0);
- //移除回调函数和消息
- case MotionEvent.ACTION_DOWN:
- mHandler.removeCallbacksAndMessages(null);
- break;
- //当你触摸时停止自动滑动
- case MotionEvent.ACTION_UP:
- autoScroll();
- break;
- }
- return false;
- }
- });
- }
- }
private void onTouch() {
//通过mViewPager去设置触摸滑动的点击事件
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mHandler.removeMessages(0);
//移除回调函数和消息
case MotionEvent.ACTION_DOWN:
mHandler.removeCallbacksAndMessages(null);
break;
//当你触摸时停止自动滑动
case MotionEvent.ACTION_UP:
autoScroll();
break;
}
return false;
}
});
}
}
Java布局:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/activity_main"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.example.android.indicatorapp.MainActivity">
- <android.support.v4.view.ViewPager
- android:id="@+id/viewpager"
- android:layout_width="match_parent"
- android:layout_height="200dp">
- </android.support.v4.view.ViewPager>
- <!--使用的自定义控件-->
- <!--setNumber 我们自己设置的属性,决定空心圆的多少,一开始不要加上-->
- <com.example.liuy.indicatorapp.Indicator
- app:setNumber="6"
- android:id="@+id/indicator"
- android:layout_width="200dp"
- android:layout_height="60dp"
- android:layout_alignBottom="@+id/viewpager"
- android:layout_centerHorizontal="true"/>
- </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.indicatorapp.MainActivity">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="200dp">
</android.support.v4.view.ViewPager>
<!--使用的自定义控件-->
<!--setNumber 我们自己设置的属性,决定空心圆的多少,一开始不要加上-->
<com.example.liuy.indicatorapp.Indicator
app:setNumber="6"
android:id="@+id/indicator"
android:layout_width="200dp"
android:layout_height="60dp"
android:layout_alignBottom="@+id/viewpager"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
接下我们创建一个 Indicator这个类的实现以下步骤:
1.首先继承View,复写构造方法
- public class Indicator extends View {
- //实心圆的画笔;
- private Paint mForePaint;
- //空心圆的画笔;
- private Paint mBgPaint;
- //规定圆的数量,默认是4个,如果有XML指定的数量,使用指定的
- private int mNumber = 4;
- //圆的半径,规定默认值为10,如果有XML指定的数量,使用指定的
- private int mRadius = 10;
- //定义圆(空心圆)的背景颜色,默认红色,如果有XML指定的数量,使用指定的
- private int mBgColor = Color.RED;
- //定义圆(实心圆)的背景颜色,默认蓝色,如果有XML指定的数量,使用指定的
- private int mkForeColor = Color.BLUE;
public class Indicator extends View {
//实心圆的画笔;
private Paint mForePaint;
//空心圆的画笔;
private Paint mBgPaint;
//规定圆的数量,默认是4个,如果有XML指定的数量,使用指定的
private int mNumber = 4;
//圆的半径,规定默认值为10,如果有XML指定的数量,使用指定的
private int mRadius = 10;
//定义圆(空心圆)的背景颜色,默认红色,如果有XML指定的数量,使用指定的
private int mBgColor = Color.RED;
//定义圆(实心圆)的背景颜色,默认蓝色,如果有XML指定的数量,使用指定的
private int mkForeColor = Color.BLUE;
2.初始化画笔,该方法在Java代码添加控件时回调
- public Indicator(Context context, AttributeSet attrs) {
- super(context, attrs);
- //初始化画笔对象
- initPaint();
- //引用atts文件下,给自己定义控件设置属性,得到TypdeArray对象.
- //参数1:attrs固定 参数2:在values文件下XML里R.styleable.下写的name名字
- TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Indicator);
- //使用typedArray对象,把在自定义控件设置的属性和XML文件里的属性进行关联,才算完成
- //参数1:R.styleable.Indicator 你在attrs定义的名字 参数2:你要管理的成员变量名(最后=也是成员变量名)
- //注意你在XML文件里设置的类型属性获取时也要是对应的类型.(最后同步Gradle文件,否则在XML布局文件里依然没有办法引用)
- mNumber = typedArray.getInteger(R.styleable.Indicator_setNumber, mNumber);
- mRadius = typedArray.getInteger(R.styleable.Indicator_setRadius, mRadius);
- }
public Indicator(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化画笔对象
initPaint();
//引用atts文件下,给自己定义控件设置属性,得到TypdeArray对象.
//参数1:attrs固定 参数2:在values文件下XML里R.styleable.下写的name名字
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Indicator);
//使用typedArray对象,把在自定义控件设置的属性和XML文件里的属性进行关联,才算完成
//参数1:R.styleable.Indicator 你在attrs定义的名字 参数2:你要管理的成员变量名(最后=也是成员变量名)
//注意你在XML文件里设置的类型属性获取时也要是对应的类型.(最后同步Gradle文件,否则在XML布局文件里依然没有办法引用)
mNumber = typedArray.getInteger(R.styleable.Indicator_setNumber, mNumber);
mRadius = typedArray.getInteger(R.styleable.Indicator_setRadius, mRadius);
}
3.复写onMeasure,方法里调用
initPaint()方法
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- initPaint();
- }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initPaint();
}
4.将
mForePaint和
mBgPaint抽取成员变量
initPaint此代码是实现画圆:
- //初始化画笔对象
- private void initPaint() {
- //创建画笔的对象
- mForePaint = new Paint();
- //设置抗锯齿(如果不设置抗锯齿画出来的图会模糊)
- mForePaint.setAntiAlias(true);
- //设置画笔的样式,为实心
- mForePaint.setStyle(Paint.Style.FILL);
- //设置画笔的颜色
- mForePaint.setColor(mkForeColor);
- //设置画笔的宽度
- mForePaint.setStrokeWidth(2);
- //创建画笔的对象,用于画空心圆
- mBgPaint = new Paint();
- mBgPaint.setAntiAlias(true);
- mBgPaint.setStyle(Paint.Style.STROKE);
- mBgPaint.setColor(mBgColor);
- mBgPaint.setStrokeWidth(2);
- }
//初始化画笔对象
private void initPaint() {
//创建画笔的对象
mForePaint = new Paint();
//设置抗锯齿(如果不设置抗锯齿画出来的图会模糊)
mForePaint.setAntiAlias(true);
//设置画笔的样式,为实心
mForePaint.setStyle(Paint.Style.FILL);
//设置画笔的颜色
mForePaint.setColor(mkForeColor);
//设置画笔的宽度
mForePaint.setStrokeWidth(2);
//创建画笔的对象,用于画空心圆
mBgPaint = new Paint();
mBgPaint.setAntiAlias(true);
mBgPaint.setStyle(Paint.Style.STROKE);
mBgPaint.setColor(mBgColor);
mBgPaint.setStrokeWidth(2);
}
5.重写
onDraw方法.参数就是
canvas画板,直接使用,画两种类型的圆
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //画多个空心圆,为了使圆不重叠,所以对X轴坐标进行动态的修改
- for (int i = 0; i < mNumber; i++) {
- //
- canvas.drawCircle(230 + i * mRadius * 3, 20, mRadius, mBgPaint);
- }
- //画实心圆,为使实心圆能够进行X轴移动 参数1加上了偏移量
- canvas.drawCircle(230 + moffest, 20, mRadius, mForePaint);
- }
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画多个空心圆,为了使圆不重叠,所以对X轴坐标进行动态的修改
for (int i = 0; i < mNumber; i++) {
//
canvas.drawCircle(230 + i * mRadius * 3, 20, mRadius, mBgPaint);
}
//画实心圆,为使实心圆能够进行X轴移动 参数1加上了偏移量
canvas.drawCircle(230 + moffest, 20, mRadius, mForePaint);
}
6.接受外界ViewPager的
Item位置,及偏移的百分比转换为偏移量:
- private float moffest;
- public void setoffest(int position, float positionOffset) {
- invalidate();
- //为了防止角标越界,取余数
- position %= mNumber;
- //给成员变量设置偏移量具体数据
- //因为从一个圆到另一个圆,要经过3个半径+偏移量*3个半径,也可以看出点的移动过程
- moffest = position * 3 * mRadius + positionOffset * 3 * mRadius;
- if (position == mNumber - 1) {
- moffest = position * 3 * mRadius;
- }
- //关键的一点,从新绘制自定义View的方法,十分常用.(不绘制看不成自定义控件的动态效果)
- invalidate();
- }
- }
private float moffest;
public void setoffest(int position, float positionOffset) {
invalidate();
//为了防止角标越界,取余数
position %= mNumber;
//给成员变量设置偏移量具体数据
//因为从一个圆到另一个圆,要经过3个半径+偏移量*3个半径,也可以看出点的移动过程
moffest = position * 3 * mRadius + positionOffset * 3 * mRadius;
if (position == mNumber - 1) {
moffest = position * 3 * mRadius;
}
//关键的一点,从新绘制自定义View的方法,十分常用.(不绘制看不成自定义控件的动态效果)
invalidate();
}
}
7.同步Gradle文件,否则在XML布局文件里依然没有办法引用
8.在values下创建一XML资源文件
9.在XML文件里,定义标头和属性
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <!--指定这些属性都是谁的的,注意这里添加完属性要在自己定义View类构造方法里应用-->
- <!--注意:写完这个属性后,布局XML文件要想应用的话,必须要同步Gradle文件-->
- <declare-styleable name="Indicator">
- <!--定义控件显示圆的数量 参数format是类型-->
- <attr name="setNumber" format="integer"></attr>
- <!--定义控件显示圆的半径 参数format是类型-->
- <attr name="setRadius" format="integer"></attr>
- </declare-styleable>
- </resources>