在实现该控件之前,先说一下该控件的难度,
一、
每个item中如果有RadioButton之类,可以focus焦点的,点击效果可能会失效
二、无限的滚动
下面是效果图:
实现上图的效果,一共自定义了两个 控件,viewpager+底部导航图标
下面我先来讲解一下,viewpager的实现:
1.初始化
<pre name="code" class="java">/** 点击按下的坐标 **/
PointF downP = new PointF();
/** 当前按下的坐标 **/
PointF curP = new PointF();
OnSingleTouchListener onSingleTouchListener;
public MyPagerAdapter adapter;
public ArrayList<View> listViews;
private Activity acitvity;
private boolean isTouch = false;
AutoScrollViewPager viewpager;
AutoScrollViewPagerStateChange stateChange;
public AutoScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
listViews = new ArrayList<View>();
viewpager = this;
adapter = new MyPagerAdapter();
viewpager.setAdapter(adapter);
initOntouch();
}<pre code_snippet_id="1780518" snippet_file_name="blog_20160723_3_7858134" name="code" class="java"><pre code_snippet_id="1780518" snippet_file_name="blog_20160723_2_1968790" name="code" class="java"><pre code_snippet_id="1780518" snippet_file_name="blog_20160723_3_7858134" name="code" class="java">listViews 存放轮播所需的图片
adapter是 viewpager的pageradapter
initOntouch()方法是设置touch事件的监听实现点击,获取当前点击的下标
通过对比touch中 down和up的点的x,y的值是否相同,相同表示点击,不同不做处理
private void initOntouch() {
// TODO Auto-generated method stub
viewpager.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg1, MotionEvent arg0) {
// TODO Auto-generated method stub
// 给当前坐标赋值
curP.x = arg0.getX();
curP.y = arg0.getY();
if (arg0.getAction() == MotionEvent.ACTION_DOWN) {
//
// 给当前按下赋值
downP.x = arg0.getX();
downP.y = arg0.getY();
// 设置 获取当前事件,通知父控件不将事件在进行分发
getParent().requestDisallowInterceptTouchEvent(true);
isTouch = true;
}
if (arg0.getAction() == MotionEvent.ACTION_MOVE) {
//
getParent().requestDisallowInterceptTouchEvent(true);
isTouch = true;
}
if (arg0.getAction() == MotionEvent.ACTION_UP) {
//
//判断是否是点击操作
<span style="color:#ff6666;">if (downP.x == curP.x && downP.y == curP.y) {
onSingleTouch(viewpager.getCurrentItem()
% listViews.size());
}</span>
isTouch = false;
} else if (arg0.getAction() == MotionEvent.ACTION_CANCEL) {
getParent().requestDisallowInterceptTouchEvent(false);
}
return false;
}
});
}<pre name="code" class="java"><span style="white-space:pre"> </span>/**
* 单击事件
*/
public void onSingleTouch(Object obj) {
if (onSingleTouchListener != null) {
onSingleTouchListener.onSingleTouch(obj);
}
}
/**
* 单机事件接口
*/
public interface OnSingleTouchListener {
public void onSingleTouch(Object obj);
}
</pre><pre code_snippet_id="1780518" snippet_file_name="blog_20160723_10_5295814" name="code" class="java">getParent().requestDisallowInterceptTouchEvent(false);该方法是通知父控件是否还要继续分发事件,true表示阻止父控件继续分发,false表示不做限制
<span style="font-family: Arial, Helvetica, sans-serif;">isTouch 表示再定時器中做处理</span>
2.无限轮播的原理
<span style="color:#ff6666;"> 原理: 假设 一共有 4张图片,当viewpager滚动到第4张得时候,图片直接显示第一张,所以在 viewpager的adapter中就要做如下处理</span>
// viewpager的 adapter
private class MyPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
// TODO Auto-generated method stub
// return listViews.size();
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return arg0 == arg1;
}
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(listViews.get(position % listViews.size()));// 删除上个图片,如果不删除的话,当 viewpager调用 <span style="font-family: Arial, Helvetica, sans-serif;">instantiateItem 方法获取 </span><span style="font-family: Arial, Helvetica, sans-serif;">position % listViews.size() 的图片时,会报 该图片已被占用</span><span style="font-family: Arial, Helvetica, sans-serif;">
</span>
}
public Object instantiateItem(ViewGroup container, int position) {
container.addView(listViews.get(position % listViews.size()), 0);//
return listViews.get(position % listViews.size());
}
}
其中 设置 让 getCount返回一个 最大值,这样在用户浏览的时候,看起来是一直在做无限轮播,(ps:如果用户如果有足够的耐心等待,图片真正的轮播完全的时候,图片就会停留到 listViews.get(Integer.MAX_VALUE % listViews.size())的那张图片,不过得很长时间)
<span style="font-family: Arial, Helvetica, sans-serif;">3.定时器</span>
<span style="font-family: Arial, Helvetica, sans-serif;"></span><pre name="code" class="java"> /**
* 定时器
*/
private Timer time;
boolean isScroll = false;
public void startScroll() {
if (isScroll)
return;
if (time == null)
time = new Timer();
time.schedule(new TimerTask() {
@Override
public void run() {
isScroll = true;
Runnable run = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
if (<span style="color:#ff6666;">isTouch</span>) {//isTouch是在touch监听接口中赋值,来监听是否是用户正在触摸屏幕,如果是将会不做任何处理,也就实现了,解决了当用户想要手动查看轮播的内容时,会自动换掉当前图片
} else {
int current = viewpager.getCurrentItem();
if (current < adapter.getCount() - 1) {
viewpager.setCurrentItem(current + 1);
} else {
viewpager.setCurrentItem(0);
}
}
}
};
viewpager.post(run);//UI的操作都要在主线程中,所以 用viewpager post方法来更改UI,否则会报错
}
}, 500, 2000);
}
public void stopScroll() {
if (time != null) {
time.cancel();
time = null;
}
}
其中 startScroll()方法是在Activity或者 Fragment中初始化之后,调用的
<span style="font-family: Arial, Helvetica, sans-serif;">3、底部指示器的绘制</span>
<pre name="code" class="java" style="font-family: Arial, Helvetica, sans-serif;">
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
/**
* viewpager 底部下标指示器
* @author ML 2015-05-29
*
*/
public class AutoScrollViewPagerStateChange extends View {
public AutoScrollViewPagerStateChange(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
private int size, current;
@SuppressLint("NewApi")
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Paint p = new Paint();
p.setAntiAlias(true);
p.setAlpha(1);
int item_width = 30;
int item_height=5;
int jiange=20;
int center_x = (getWidth() - item_width * size-jiange*(size)) / 2;
int lastright = 0;
for (int i = 0; i < size; i++) {
p.setColor(Color.parseColor("#ff00ff"));//表示不是当前页面的颜色
p.setStyle(Style.FILL);
if (i == current) {
p.setStyle(Style.FILL);
p.setColor(Color.WHITE);<span style="font-family: Arial, Helvetica, sans-serif;">//表示 当前页面的颜色</span>
}
<span style="font-family: Arial, Helvetica, sans-serif;"> /*canvas.drawCircle(center_x + item_width / 2 + i * item_width,</span><span style="font-family: Arial, Helvetica, sans-serif;">getHeight() / 2, 10, p);*/
</span><span style="font-family: Arial, Helvetica, sans-serif;"> int left=center_x + item_width / 2 + i * item_width</span><span style="font-family: Arial, Helvetica, sans-serif;">
if(i!=0)left=lastright+jiange;
</span><pre code_snippet_id="1780518" snippet_file_name="blog_20160723_24_8667830" name="code" class="java" style="font-family: Arial, Helvetica, sans-serif;"> int right=left+item_width;
lastright=right;
int top=0;
int bottom=item_height;
Rect r=new Rect(left,top,right,bottom);
canvas.drawRect(r, p);
}
canvas.save();
}
public void drawCicle(int size, int current) {
this.size = size;
this.current = current;
invalidate();
}
}
<span style="font-family:Arial, Helvetica, sans-serif;">指示器是通过 canvas paint 来绘制的 通过计算 每一个 长方形的 起始位置 来动态的 改变指示器,在当前下标的 长方形哪里改变一下 paint的颜色值,就可以实现底部切换的效果</span>
如有错误,欢迎指正
源代码:https://github.com/MengLeiGitHub/AutoScrollViewPager