您的位置:首页 > 其它

复杂自定义控件---自定义ViewPager的实现

2016-06-16 17:58 549 查看
效果图



核心方法

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);
}
});

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: