效果图
核心方法
1、三个构造方法(一个参数, 两个参数, 三个参数)
2、onMesure 测量控件
4、onLayout 分配控件布局
5、computeScroll()
计算滑动
6、onDraw 绘制控件
7、onTouchEvent()
中断事件传递
8、dispatchTouchEvent
分发事件
实现步骤:
1 初始化显示的数据
//为MyViewPager添加图片 for(int i=0; i<imgs.length; i++) { ImageView imageView = new ImageView(getApplicationContext()); imageView.setBackgroundResource(imgs[i]); mMyViewPager.addView(imageView); } View view = View.inflate(getApplicationContext(), R.layout.ll_view, null); mMyViewPager.addView(view, 2);
2 测量控件 (注:由于控件的嵌套复杂性不同,导致系统测量的次数不一样,嵌套布局越多测量 越复杂,所以在使用布局时尽量避免嵌套的层次)
<strong><span style="color:#ff0000;">// onMeasure 会在onLayout 之前调用 // 要求父容器一定要测量子容器 ,如果不测量 子容器 子容器宽和高 都是0 子容器由于挂载到父容器可以正常显示,但是 孙子就不能显示 // 父容器先知道自己大小(match_parent) 子容器先知道大小(wrap_content) //widthMeasureSpec不仅表示控件的宽,里面还带有控件的属性的基本信息</span></strong> @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); System.out.println("onMeasure"); System.out.println(widthMeasureSpec); MeasureSpec.getMode(widthMeasureSpec); //获取控件的模型 MeasureSpec.getSize(widthMeasureSpec); // 得到控件真正的尺寸 System.out.println(heightMeasureSpec); for(int i=0; i< getChildCount(); i++) { View view = getChildAt(i); view.measure(widthMeasureSpec, heightMeasureSpec);// 对每个孩子都测量 } }
3 分配控件显示的位置
// 分配孩子位置 在onDraw方法之前调用 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for(int i=0; i<getChildCount(); i++) { View view = getChildAt(i); view.layout(0 + getWidth() * i, 0, getWidth() + getWidth() * i, getHeight()); } }
4 让控件随着手指的移动而移动
//手势识别监听器 private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener { //滑动事件 //distanceX x轴滑动的距离 //distanceY y轴滑动的距离 @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { scrollBy((int)distanceX, 0); //让viewGroup 移动多少距离 //scrollBy 会自动调用invalidate() 该方法 //invalidate(); 自动调用onDraw return super.onScroll(e1, e2, distanceX, distanceY); } }
private void initView() { mGestureDetector = new GestureDetector(getContext(), new MySimpleOnGestureListener()); }
@Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); //把手势识别器注册到触摸事件中 switch(event.getAction()) { case MotionEvent.ACTION_DOWN: // 当手指按下的时候 记录开始的坐标 startX = (int) event.getX(); break; case MotionEvent.ACTION_UP: // 当手指抬起的时候 记录结束的坐标 int endX = (int) event.getX(); if((startX - endX) > getWidth() / 2) { //进入下一个界面 index++; } else if((endX - startX) > getWidth() / 2) { // 进入上一个界面 index--; } moveToIndex(); break; default: break; } //返回处理了触摸事件 return true; }
5 自动跳转界面(根据手势滑动的距离,确定页面跳转)
private void moveToIndex() { if(index < 0) { index = 0; } if(index == getChildCount()) { index = getChildCount() -1; } if(mOnpageChangedListener != null) { mOnpageChangedListener.onChange(index); } mScroller = new Scroller(getContext()); mScroller.startScroll(getScrollX(), getScrollY(), (int)(getWidth() * index - getScrollX()), 0); invalidate(); }
6 移动(手指抬起时确定要跳转的页面后,慢慢的实现页面移动到指定的位置)
private void moveToIndex() { if(index < 0) { index = 0; } if(index == getChildCount()) { index = getChildCount() -1; } if(mOnpageChangedListener != null) { mOnpageChangedListener.onChange(index); } mScroller = new Scroller(getContext()); mScroller.startScroll(getScrollX(), getScrollY(), (int)(getWidth() * index - getScrollX()), 0); invalidate(); } //计算移动 每次刷新界面 该方法都会被调用 //scroller.computeScrollOffset() 返回值是true 情况下 代表动作没有结束 @Override public void computeScroll() { if(mScroller != null) { if(mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), 0); //scrollTo这个方法一执行 会调用invalidate();,异步执行 invalidate(); } } super.computeScroll(); }
7 中断事件(当左右移动的控件里嵌套了上下移动的空间--ScrollView 应该判断,当前的手势响应是否被中断,通过判断,当前使用者的意图,确定是左走滑动,还是要上下滑动,来决定是否中断手势事件的向下传递)
// 中断事件传递 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch(ev.getAction()) { case MotionEvent.ACTION_DOWN: mGestureDetector.onTouchEvent(ev); // 避免了中断事件 导致没有处理按下的操作 startX2 = (int) ev.getX(); startY2 = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: // 手指移动的事件 int endX2 = (int) ev.getX(); int endY2 = (int) ev.getY(); int dx = endX2 - startX2; // x轴的偏移量 int dy = endY2 - startY2; //y轴的偏移量 if(Math.abs(dx) > Math.abs(dy)) { //如果为左右移动则中断手势事件响应的传递 return true; } break; default: break; } // 如果是上下滑动 屏幕的时候 不中断事件 // return false; //如果是左右滑动 中断事件 // return true; //交给父类判断(即交给ViewGroup判断)父类的该方法返回值为false 不中断事件 return super.onInterceptTouchEvent(ev); }
android中Touch事件处理流程图:
Touch事件传递机制流程图:
8 回调方法(当跳转到ViewPager中的某一页时,会自动触发某个事件实现接口回调)
// 定义一个公开接口,设置回调方法 public interface OnPageChangedListener { void onChange(int index); } private OnPageChangedListener mOnpageChangedListener; //定义一个公开的注册页面改变的方法 public void setOnpageChangedListener(OnPageChangedListener listener) { mOnpageChangedListener = listener; }
private void moveToIndex() { if(index < 0) { index = 0; } if(index == getChildCount()) { index = getChildCount() -1; } if(mOnpageChangedListener != null) { mOnpageChangedListener.onChange(index); } mScroller = new Scroller(getContext()); mScroller.startScroll(getScrollX(), getScrollY(), (int)(getWidth() * index - getScrollX()), 0); invalidate(); }
完整代码:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.mhy.zidingyiviewpager.MainActivity"> <RadioGroup android:id="@+id/mRadioGroup" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > </RadioGroup> <com.example.mhy.zidingyiviewpager.MyViewPager android:id="@+id/mViewPager" android:layout_width="match_parent" android:layout_height="match_parent" > </com.example.mhy.zidingyiviewpager.MyViewPager> </LinearLayout>
ll_view.xml
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" > <requestFocus /> </EditText> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" > <requestFocus /> </EditText> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" > <requestFocus /> </EditText> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" /> </LinearLayout> </ScrollView>
MyViewPager.java
package com.example.mhy.zidingyiviewpager; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; /** * Created by mhy on 2016/6/15. */ public class MyViewPager extends ViewGroup { private GestureDetector mGestureDetector; private Scroller mScroller; public MyViewPager(Context context) { super(context); // 创建手势识别器 initView(); } // 如果没有两个参数构造方法 是不允许在布局文件中声明控件 public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); // 创建手势识别器 initView(); } public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 创建手势识别器 initView(); } //手势识别监听器 private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener { //滑动事件 //distanceX x轴滑动的距离 //distanceY y轴滑动的距离 @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { scrollBy((int)distanceX, 0); //让viewGroup 移动多少距离 //scrollBy 会自动调用invalidate() 该方法 //invalidate(); 自动调用onDraw return super.onScroll(e1, e2, distanceX, distanceY); } } private void initView() { mGestureDetector = new GestureDetector(getContext(), new MySimpleOnGestureListener()); } // 定义一个公开接口,设置回调方法 public interface OnPageChangedListener { void onChange(int index); } private OnPageChangedListener mOnpageChangedListener; //定义一个公开的注册页面改变的方法 public void setOnpageChangedListener(OnPageChangedListener listener) { mOnpageChangedListener = listener; } // 中断事件传递 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch(ev.getAction()) { case MotionEvent.ACTION_DOWN: mGestureDetector.onTouchEvent(ev); // 避免了中断事件 导致没有处理按下的操作 startX2 = (int) ev.getX(); startY2 = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: // 手指移动的事件 int endX2 = (int) ev.getX(); int endY2 = (int) ev.getY(); int dx = endX2 - startX2; // x轴的偏移量 int dy = endY2 - startY2; //y轴的偏移量 if(Math.abs(dx) > Math.abs(dy)) { //如果为左右移动则中断手势事件响应的传递 return true; } break; default: break; } // 如果是上下滑动 屏幕的时候 不中断事件 // return false; //如果是左右滑动 中断事件 // return true; //交给父类判断(即交给ViewGroup判断)父类的该方法返回值为false 不中断事件 return super.onInterceptTouchEvent(ev); } private int startX2; private int startY2; private int index = 0; // 当前显示的位置 private int startX; @Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); //把手势识别器注册到触摸事件中 switch(event.getAction()) { case MotionEvent.ACTION_DOWN: // 当手指按下的时候 记录开始的坐标 startX = (int) event.getX(); break; case MotionEvent.ACTION_UP: // 当手指抬起的时候 记录结束的坐标 int endX = (int) event.getX(); if((startX - endX) > getWidth() / 2) { //进入下一个界面 index++; } else if((endX - startX) > getWidth() / 2) { // 进入上一个界面 index--; } moveToIndex(); break; default: break; } //返回处理了触摸事件 return true; } //外界通过指定索引将页面切换到指定的位置 public void moveToIndex(int index) { this.index = index; moveToIndex(); } private void moveToIndex() { if(index < 0) { index = 0; } if(index == getChildCount()) { index = getChildCount() -1; } if(mOnpageChangedListener != null) { mOnpageChangedListener.onChange(index); } mScroller = new Scroller(getContext()); mScroller.startScroll(getScrollX(), getScrollY(), (int)(getWidth() * index - getScrollX()), 0); invalidate(); } //计算移动 每次刷新界面 该方法都会被调用 //scroller.computeScrollOffset() 返回值是true 情况下 代表动作没有结束 @Override public void computeScroll() { if(mScroller != null) { if(mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), 0); //scrollTo这个方法一执行 会调用invalidate();,异步执行 invalidate(); } } super.computeScroll(); } // onMeasure 会在onLayout 之前调用 // 要求父容器一定要测量子容器 ,如果不测量 子容器 子容器宽和高 都是0 子容器由于挂载到父容器可以正常显示,但是 孙子就不能显示 // 父容器先知道自己大小(match_parent) 子容器先知道大小(wrap_content) //widthMeasureSpec不仅表示控件的宽,里面还带有控件的属性的基本信息 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); System.out.println("onMeasure"); System.out.println(widthMeasureSpec); MeasureSpec.getMode(widthMeasureSpec); //获取控件的模型 MeasureSpec.getSize(widthMeasureSpec); // 得到控件真正的尺寸 System.out.println(heightMeasureSpec); for(int i=0; i< getChildCount(); i++) { View view = getChildAt(i); view.measure(widthMeasureSpec, heightMeasureSpec);// 对每个孩子都测量 } } // 分配孩子位置 在onDraw方法之前调用 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for(int i=0; i<getChildCount(); i++) { View view = getChildAt(i); view.layout(0 + getWidth() * i, 0, getWidth() + getWidth() * i, getHeight()); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } }
MainActivity.java
package com.example.mhy.zidingyiviewpager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import android.widget.RadioButton; import android.widget.RadioGroup; public class MainActivity extends AppCompatActivity { private MyViewPager mMyViewPager; private RadioGroup mRadioGroup; private int[] imgs = new int[] { R.mipmap.a1, R.mipmap.a2, R.mipmap.a3, R.mipmap.a4, R.mipmap.a5, R.mipmap.a6}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mMyViewPager = (MyViewPager) findViewById(R.id.mViewPager); mRadioGroup = (RadioGroup) findViewById(R.id.mRadioGroup); //为MyViewPager添加图片 for(int i=0; i<imgs.length; i++) { ImageView imageView = new ImageView(getApplicationContext()); imageView.setBackgroundResource(imgs[i]); mMyViewPager.addView(imageView); } View view = View.inflate(getApplicationContext(), R.layout.ll_view, null); mMyViewPager.addView(view, 2); System.out.println("mMyViewPager.getChildCount() " + mMyViewPager.getChildCount()); for(int i=0; i<mMyViewPager.getChildCount(); i++) { RadioButton radioButton = new RadioButton(getApplicationContext()); radioButton.setId(i); mRadioGroup.addView(radioButton); if(i == 0) { radioButton.setChecked(true); } } //监听页面切换事件,使对应的单选按钮做相应的改变 mMyViewPager.setOnpageChangedListener(new MyViewPager.OnPageChangedListener() { @Override public void onChange(int index) { RadioButton radioButton = (RadioButton) mRadioGroup.getChildAt(index); radioButton.setChecked(true); } }); //监听单选按钮更改的事件,使ViewPager页面更随做相应的切换 mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { mMyViewPager.moveToIndex(checkedId); } }); } }
时间: 2024-10-20 16:58:24