效果图
核心方法
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);
}
});
}
}