复杂自定义控件---自定义ViewPager的实现
2016-06-16 17:58
549 查看
效果图
![](https://img-blog.csdn.net/20160616172309049)
核心方法
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);
3 分配控件显示的位置
4 让控件随着手指的移动而移动
7 中断事件(当左右移动的控件里嵌套了上下移动的空间--ScrollView 应该判断,当前的手势响应是否被中断,通过判断,当前使用者的意图,确定是左走滑动,还是要上下滑动,来决定是否中断手势事件的向下传递)
![](https://img-blog.csdn.net/20160616174322025)
Touch事件传递机制流程图:
![](https://img-blog.csdn.net/20160616174427027)
8 回调方法(当跳转到ViewPager中的某一页时,会自动触发某个事件实现接口回调)
完整代码:
activity_main.xml
ll_view.xml
MyViewPager.java
核心方法
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;MainActivity.java
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);
}
}
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); } }); } }
相关文章推荐
- Java并发编程:Lock
- get utc+8 当时时间
- 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)
- 损失函数
- 【HTML/JS】实现iFrame自适应高度,原来很简单!
- jenkins
- Linux crontab使用详解
- 机器学习算法汇总:人工神经网络、深度学习及其它
- 十一.列表生成式
- php获取文件创建时间、修改时间、访问时间
- spark streaming kafka1.4.1中的低阶api createDirectStream使用总结(转)
- 16进制
- python练习1-猜数游戏
- Android Camera 调用流程
- 个人信息
- http web 返回码概念
- php中在局部作用域内访问全局变量
- 公钥和秘钥
- 南京工业大学校园无线一账号多设备共享上网的方式
- jdk和jre的区别!