仿ViewPager

继承已有ViewGroup实现自定义控件 ----仿 Viewpager组件
   
实现步骤:
1、自定义view继承viewGroup。
2、重写onLayout方法,为每一个子View确定位置。
3、重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view,
4、监听UP事件,当手指抬起时,判断应显示的页面位置,并计算距离、滑动页面。
5、添加页面切换的监听事件。
 首先,来看下 activity_main.xml 布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ........>
    <RadioGroup
        android:id="@+id/radioGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" ></RadioGroup>
    <com.itheima.myscrollview.MyScrollView
        android:id="@+id/myscroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
包含 RadioGroup 和 一个自定义的控件 MyScrollView。 为了减少帖的代码,一些就省略。
 1. 创建  MyScrollView 继承viewGroup 
  public class MyScrollView extends ViewGroup{ ..... }
① 复写 onLayout方法,为每一个子View确定位置。
/**对子view进行布局,父view 会根据子view的需求,和自身的情况,来综合确定子view的位置,(确定他的大小)
* changed  若为true ,说明布局发生了变化
* l\t\r\b\  左,上,右,下,是指在viewGround坐标系中的位置
*/
protected void onLayout(boolean changed, int l, int t, int r, int b) {	
	for (int i = 0; i < getChildCount(); i++) {
	View view = getChildAt(i); // 取得下标为I的子view		
	view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());//指定坐标位置		
//	view.getWidth();  //得到view的真实的大小。
	}		
}
问1:怎么把我们想要的内容放在我们自定义的MyScrollView中呢?
答:这就要实现类似PagerAdapter的东西---在MainActivity中初始化一些ImageView (也可是其它布局,用getLayoutInflater().inflate(R.layout.temp, null);得到View),然后把它们加入到我们的组件中 myScrollView.addView(view,number);
for (int i = 0; i < ids.length; i++) {
	ImageView image = new ImageView(this);
	image.setBackgroundResource(ids[i]);
	msv.addView(image);
}
现在就把图片添加到  MyScrollView 中了。运行就可以看到图片了,但是还不能滑动,因为还没有添加touch事件的方法。
② 给 MyScrollView 添加touch事件处理方法
public boolean onTouchEvent(MotionEvent event) {
	super.onTouchEvent(event);
	/**ACTION_DOWN=0;ACTION_UP=1;ACTION_MOVE=2;  */
          gesDetector.onTouchEvent(event); // 把touch事件交给GestureDetector来解析
          // 添加自己的事件解析
          switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
          	downX = (int) event.getX();
          	break;
          case MotionEvent.ACTION_MOVE:
          	break;
          case MotionEvent.ACTION_UP:
          	if (!isFling) {// 在没有发生快速滑动的时候,才执行按位置判断currid
          		int nextId = 0;
          		if (event.getX() - downX > getWidth() / 2) {// 手指向右滑动,超过屏幕的1/2
          			// 当前的currid - 1
          			if (currId >= 1) { // 防止它成负值
          				nextId = currId - 1;
          			}
          		} else if (downX - event.getX() > getWidth() / 2) { // 手指向左滑动,超过屏幕的1/2
          			// 当前的currid + 1
          			nextId = currId + 1;
          		} else {
          			nextId = currId;
          		}
          		move2Dest(nextId);
          	}
	/**注意这里要更新状态,否则会出乱七八糟的问题,画面跳动的BUG没反应就是这引起的**/
		isFling = false;  
		// scrollTo(0, 0); //实现一松手就回去(0,0)点
		break;
	default:
		break;
	}
	return true;
}
这里,把  MotionEvent 也传递给了GestureDetector 对象,让它也监听手势。
③ onScroll() 方法
/** * 响应手指在屏幕上的滑动事件 e1 down时的事件 e2 move时的事件 */
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,	float distanceX, float distanceY) {
/** 移动当前view内容 移动一段距离 disX X方向移的距离 为正是,图片向左移动,为负时,图片向右移动 disY * Y方向移动的距离 */
	scrollBy((int) distanceX, 0);
	/** 将当前视图的基准点移动到某个点 坐标点 x 水平方向X坐标 Y 竖直方向Y坐标 scrollTo(x, y); */
	return false;
}
 增加快速划动时也能翻页:
/** * 发生快速滑动时的回调 */
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,	float velocityY) {
	isFling = true;
	if(velocityX>0 && currId>0){ // 快速向右滑动
		currId--;
	}else if(velocityX<0 && currId<getChildCount()-1){ // 快速向左滑动
		currId++;
	}	
	moveToDest(currId);
	return false;
}
现在可以移动了,但是我们想改变它的移动效果,让它匀速移动。这就要写个计算类,来计算它应该移动多少距离与速度 ----MyScroller。
④ 我们往 MyScrollView 中添加一个ListView
//给自定义viewGroup添加测试的布局
View temp = getLayoutInflater().inflate(R.layout.temp, null);
msv.addView(temp, 2);
运行后,发现它上面的控件看不见,这是怎么回事呢?
原来是在 MyScrollView 没有为子View分配大小。复写onMeasure(),为每个控件分配大小。
/** * 计算 控件大小, 做为viewGroup 还有一个责任,,:计算 子view的大小 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);	
	int size = MeasureSpec.getSize(widthMeasureSpec);
	int mode = MeasureSpec.getMode(widthMeasureSpec);	
	for (int i = 0; i < getChildCount(); i++) {
		View v = getChildAt(i);
		v.measure(widthMeasureSpec, heightMeasureSpec);		
//			v.getMeasuredWidth() // 得到测量的大小
	}		
}
⑤ 怎么让子view中的listview能上下划,我们的  MyScrollView 又能左右划呢?
就要用到中断Touch事件--事件的分发机制,在onInterceptTouchEvent()中判断它划动的x和y轴坐标哪个更大。
 注意:    当onInterceptTouchEvent拦截后,onTouchEvent第一个检测到的事件是move事件,因为down的时候已经过去了。
/** 是否拦截事件的传递
 * 返回true时 拦截事件,执行自己的onTouchEvent方法
 * 返回false时,不拦截,也不执行自己的onTouchEvent方法 **/
public boolean onInterceptTouchEvent(MotionEvent ev) {
	boolean result = false;
	switch (ev.getAction()) {
	case MotionEvent.ACTION_DOWN:
		// 解决点击的时候有跳动的Bug,因为onTouchEvent的Down事件检测不到
		gesDetector.onTouchEvent(ev); 
		downX=(int) ev.getX();
		downY = (int) ev.getY();		
		break;
	case MotionEvent.ACTION_MOVE:
		//手指在屏幕上水平移动的绝对值
		int disX=(int) Math.abs(ev.getX()-downX);
		//手指在屏幕上Y轴移动的绝对值
		int disY=(int) Math.abs(ev.getY()-downY);
		if(disX>disY && disX>10){
			result=true;
		}else{
			result= false;
		}		
		break;
	case MotionEvent.ACTION_UP:
		break;
	}
	return result; // 返回true,listView就不能上下划了
}
⑥ 怎么让RadioButton 跟 MyScrollView 同步呢?
给 MyScrollView 添加一个页面切换的监听接口
/*** 页面改时时的监听接口   */
public interface MyPageChangedListener {
	void moveToDest(int currid);
}
private MyPageChangedListener pageChangedListener; //生成一个引用
public MyPageChangedListener getPageChangedListener() {
	return pageChangedListener;
}
public void setPageChangedListener(MyPageChangedListener pageChangedListener) {
	this.pageChangedListener = pageChangedListener;
}
 让监听事件在哪触发呢? 我们这里让它在 move2Dest()中触发。
public void move2Dest(int nextId) {
	/* 对 nextId 进行判断 ,确保 是在合理的范围 即 nextId >=0 && next <=getChildCount()-1 */
	// 确保 currId>=0
	currId = (nextId > 0) ? nextId : 0;
	// 确保 currId<=getChildCount()-1
	currId = (nextId <= getChildCount() - 1) ? nextId: (getChildCount() - 1);
	// 瞬间移动
	// scrollTo(currId * getWidth(), 0); //记得把这句注释掉
	// 触发listener事件
	if (getPageChangedListener() != null) {
		getPageChangedListener().moveToDest(currId);
	}
	/*** 下面我们把移动的效果做得好一点	 **/
	int distance = currId * getWidth() - getScrollX(); // 最终的位置 - 现在的位置 =
	// 设置运行的时间,要把myScroller 换成系统的Sroller
	myScroller.startScroll(getScrollX(), 0, distance, 0, Math.abs(distance));
	/** 刷新当前view 会触发 onDraw()方法 的执行,还会触发computeScroll()	 */
	invalidate();
}
在MainActivity中调用监听,实现接口的 moveToDest()
msv.setPageChangedListener(new MyPageChangedListener() {	
	@Override
	public void moveToDest(int currid) {
		((RadioButton)radioGroup.getChildAt(currid)).setChecked(true);
	}
});
radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {	
	@Override
	public void onCheckedChanged(RadioGroup group, int checkedId) {
		msv.move2Dest(checkedId);		
	}
});
for (int i = 0; i < msv.getChildCount(); i++) {
	//添加radioButton
	RadioButton rbtn = new RadioButton(this);
	rbtn.setId(i);	
	radioGroup.addView(rbtn);
	if(i == 0){
		rbtn.setChecked(true);
	}
}
当为 MyScrollView 对象 msv设置了MyPageChangedListener接口实现后,会在MyScrollView 的move2Dest()方法中执行,因为设置在这个方法中触发的。

注: getScrollX()表示的是当前的屏幕x坐标的最小值-移动的距离(向右滑动时移动的距离为负值,向左滑动时移动的距离为正值);


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值