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

效果图


核心方法

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

    }
}





阅读更多
换一批

没有更多推荐了,返回首页