Android 自定义控件----自定义ViewPager--学习笔记

本文详细介绍了如何在Android中自定义ViewPager,包括XML布局、手势识别器的实现、页面回弹的平滑处理、RadioGroup配合切换页面、点击事件接口的使用、处理事件冲突以及理解scroll相关方法。通过这些步骤,你可以创建一个功能完善的自定义ViewPager。
摘要由CSDN通过智能技术生成

1.自定义ViewPager

xml文件

    <com.example.viewpager2.MyViewPager
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:id="@+id/MyViewPager"

/>

实例化 

public class MainActivity extends Activity {
    private MyViewPager MyViewPager;
    int ids[]={R.drawable.a1,R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a5,R.drawable.a6};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewPager = (MyViewPager) findViewById(R.id.MyViewPager);

        //添加页面
        for (int i = 0; i < ids.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(ids[i]);

            //添加到MyViewPager这个View中
            MyViewPager.addView(imageView);
        }
}

}

指定到对应的图片上

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //遍历孩子,给每个孩子指定在屏幕上的坐标位置
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            childView.layout(i * getWidth(), 0, (i + 1) * getWidth(), getHeight());

        }

    }

 

2.手势识别器

* 1.定义出来

private GestureDetector detector;

* 2.实例化-把想要的方法重新

    private void initView(final Context context) {
        detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public void onLongPress(MotionEvent e) {
                Toast.makeText(context, "长按", Toast.LENGTH_SHORT).show();
                super.onLongPress(e);
            }

            /**
             *
             * @param e1
             * @param e2
             * @param distanceX 在X轴上滑动的距离
             * @param distanceY 在轴上滑动的距离
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                Toast.makeText(context, "滚动", Toast.LENGTH_SHORT).show();
                /**
                 * X:要在X轴移动的距离
                 * Y:要在Y轴移动的距离
                 * */
                scrollBy((int) distanceX, 0);
                return true;
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                Toast.makeText(context, "双击", Toast.LENGTH_SHORT).show();
                return super.onDoubleTap(e);
            }
        });
    }

* 3.在OnTouchEvent()把事件传递给手势识别器

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        //3.把事件传递给手势识别器
        detector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;

    }

 

3.scrollBy  正值:左移

                  负值:右移

   scrollTo  :起始值+要移动的距离 = 要移动到那里去 ,根据坐标移动

 

4.回弹 上下页面

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        //3.把事件传递给手势识别器
        detector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.w("www","onTouchEvent == ACTION_DOWN");
                //1.记录坐标
                startX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www","onTouchEvent == ACTION_MOVE");

                break;
            case MotionEvent.ACTION_UP:
                Log.w("www","onTouchEvent == ACTION_UP");
                //2.来到新的坐标
                enddX = event.getX();
                //下标位置
                int tempIndex = currentIndex;
                if ((startX - enddX) > getWidth() / 2) {
                    //显示下一个页面
                    tempIndex++;
                } else if ((enddX - startX) > getWidth() / 2) {
                    //显示上一个页面
                    tempIndex--;
                }
                //根据下标位置移动到指定页面
                scrollToPager(tempIndex);

                break;
        }
        return true;

    }
    /**
     * 屏蔽非法值,根据下标位置移动到指定页面
     *
     * @param tempIndex
     */
    public void scrollToPager(int tempIndex) {
        if (tempIndex < 0) {
            tempIndex = 0;

        } else if (tempIndex > getChildCount() - 1) {
            tempIndex = getChildCount() - 1;
        }
        //当前变量的下标位置
        currentIndex = tempIndex;

        //移动到指定位置
        scrollTo(currentIndex*getWidth(),0);

    }

5.解决回弹生硬

a=页面 * getWidth() ,页面*屏幕的宽度

b=getScrollX() ,手指放下的位置,也是回弹的开始位置

c=a-b,页面回弹的总距离

int distanceX = currentIndex * getWidth() - getScrollX();

定义一个 MyScroller 类,传入X轴的起始位置(getScrollX()),Y轴的起始位置(0),在X轴上要移动的距离(distanceX),在Y轴上要移动的距离(0)。

MyScroller scroller = new MyScroller();
scroller.startScroll(getScrollX(), getScrollY(), distanceX, 0);

设置回弹的总时间为300ms,那么 速度=c/300

移动一小段的距离= 移动这一小段所花的时间 *(c/300)

利用系统的时间 SystemClock.uptimeMillis() 来计算 移动这一小段所花的时间。

 

boolean isFinish;//true移动结束,false:移动完成

computerScrollOffset() ; //true:正在移动 false:移动结束

当移动结束的时候isFinish = true;此时computerScrollOffset()返回false,移动结束。

public class MyScroller {
    private float startX;//X轴的起始位置
    private float startY;//Y轴的起始位置
    private int distanceX;//在X轴上要移动的距离
    private int distanceY;//在Y轴上要移动的距离
    private long startTime;//开始的时间
    private boolean isFinish;//true移动结束
    private  long totalTime=300;//总时间

    public float getCurrX() {
        return currX;
    }

    //得到坐标
    public void setCurrX(float currX) {
        this.currX = currX;
    }

    public float currX;

    public void startScroll(float startX, float startY, int distanceX, int distanceY){
        this.startX=startX;
        this.startY=startY;
        this.distanceX=distanceX;
        this.distanceY=distanceY;
        this.startTime= SystemClock.uptimeMillis();//系统开机时间
        this.isFinish=false;
    }

    /**
     * 速度
     * 求移动一小段的距离
     * 求移动一小段对应的坐标
     * 求移动一小段对应的时间
     * true:正在移动
     * false:移动结束
     * @return
     */
       public  boolean computerScrollOffset(){
        if(isFinish){
            return  false;
        }

        //这一小段结束的时间
        long endTime = SystemClock.uptimeMillis();

        //移动一小段所花的时间
        long passTime = endTime - startTime;
        if(passTime < totalTime){
            //还没有移动结束
            //计算平均速度
//            float voleCity = distanceX / totalTime;
            //移动这一小段对应的距离
            float distanceSmallX = passTime * distanceX / totalTime ;

            //移动这一小段后对应的坐标
            currX = startX + distanceSmallX;  //起始位置+移动的距离
        }else{
            //移动结束
            isFinish = true;
            currX = startX + distanceX; //移动结束。起始位置+移动的距离(此时达到最大)
        }
        return  true;
    }
}
//刷新
invalidate();

//导致onDraw(),computerScroll()执行 

递归调用,直到距离到达总距离

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(scroller.computerScrollOffset()){
            float currX= scroller.getCurrX();
            scrollTo((int)currX,0);
            invalidate();
    }

}

 

6.添加RadioGroup 实现页面切换

布局:

    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_horizontal"
        android:layout_gravity="center_horizontal"
        android:id="@+id/radio"
        >

    </RadioGroup>

实例化:

 private RadioGroup radio;
radio = (RadioGroup) findViewById(R.id.radio);

设置radiogroup选中状态的变化:点击哪一个radiobutton就选中对应的页面。 

        for (int i = 0; i < MyViewPager.getChildCount(); i++) {
            RadioButton button = new RadioButton(this);
            button.setId(i);
            //添加到radiogroup
            radio.addView(button);

            //默认选择第一个页面
            radio.check(0);

            //设置radiogroup选中状态的变化
            radio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                /**
                 * @param group
                 * @param checkedId 0~5之间
                 */
                @Override
                public void onCheckedChanged(RadioGroup group, int checkedId) {
                    //根据下标位置定位到具体每个页面
                    MyViewPager.scrollToPager(checkedId);
                }
            });

        }

 

7.设置点击哪一个页面就选中对应的radiobutton----利用接口回传。 

首先学习一下点击事件的接口和调用。

7.1.定义接口

public interface OnClickListener{

     Void onClick(View v);

}

//设置监听页面的改变
public interface OnPagerChangListener{
    /**
     * 当页面改变的时候回调这个方法
     * @param position 当前页面的下标
     * @return
     */
    void onScrollToPager(int position);
}

让使用者传递接口的实例进来

public void setOnClickListener(@Nullable OnClickListener l){

     If(!isClickable()){

       setClickable(true);

}

setListenerInfo().mOnClickListener = l;

}

private OnPagerChangListener mOnPagerChangListener;

/**
 * 设置页面改变的监听
 * @param l
 */
public void setOnPagerChangListener(OnPagerChangListener l){
    mOnPagerChangListener = l;
}

7.2.调用方法

li.mOnCLickListener.onClick(this);

if(mOnPagerChangListener != null){
    mOnPagerChangListener.onScrollToPager(currentIndex);
}

哪个地方有变化就在哪里写接口

视图有变化,在视图里面写接口

 

7.3.用户使用

View.setOnClickListener(new View.OnClickListener(){

@Override

Public void onClick(View v){

}

});

 

7.4.回调

view.setOnClickListener(new View.OnClickListener(){

@Override

Public void onClick(View v){

//点击事件就被执行

}

});

//设置监听页面的改变
MyViewPager.setOnPagerChangListener(new MyViewPager.OnPagerChangListener(){
    /**
     * 0~5
     * @param position 当前页面的下标
     * @return
     */
    @Override
    public void onScrollToPager(int position) {
        radio.check(position);
    }

 

8.添加一个页面 

test.xml  上面是三个进度条,下面是一个scrollview,里面包含很多个textview。

        //添加test.xml页面
        View testView = View.inflate(this,R.layout.test,null);
        MyViewPager.addView(testView,2);

 记得onMeadure()

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for(int i = 0;i<getChildCount();i++){
            View child = getChildAt(i);
            child.measure(widthMeasureSpec,heightMeasureSpec);

        }
    }

 

9.事件冲突

scrollview 和 viewpager

问题:可以滑动scrollview,不能滑动viewpager。(上下滑动 和 左右滑动的冲突)

scrollview 把事件处理了

解决:

onInterceptTouchEvent()拦截事件:如果当前方法返回true,拦截事件,将会触发当前控件的onTouchEvent,如果当前返回false,不拦截事件,事件继续分发。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        super.onInterceptTouchEvent(ev);
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

问题:可以滑动viewpager,不能滑动scrollview。

解决:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        super.onInterceptTouchEvent(ev);
        detector.onTouchEvent(ev);
        boolean result = false;//默认传递给孩子
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.w("www","onInterceptTouchEvent == ACTION_DOWN");
                //1.记录坐标
                downX = ev.getX();
                downY = ev.getY();

                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www","onInterceptTouchEvent == ACTION_MOVE");
                //2.记录结束值
                float endX = ev.getX();
                float endY = ev.getY();

                //3.计算绝对值
                float distanceX = Math.abs(endX - downX);
                float distanceY = Math.abs(endY - downY);

                if(distanceX > distanceY && distanceX > 5 ){
                    result = true;
                }

                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return result;
    }

解决完:滑动会闪

把onInterceptTouchEvent事件传给手势识别器

 

10.事件传递机制

11.关于scrollTo,scrollBy,startX,startY,getScrollX(),getScrollY()

getScrollX=0,childView. getScrollX()会随着移动改变它的值。

源码:

MyViewPager

public class MyViewPager extends ViewGroup {
    private MyScroller scroller;

    /**
     * 手势识别器
     * 1.定义出来
     * 2.实例化-把想要的方法重新
     * 3.在OnTouchEvent()把事件传递给手势识别器
     *
     * @param context
     * @param attrs
     */
    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    private GestureDetector detector;

    //2.实例化
    private void initView(final Context context) {
        scroller = new MyScroller();
        detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public void onLongPress(MotionEvent e) {
                Toast.makeText(context, "长按", Toast.LENGTH_SHORT).show();
                super.onLongPress(e);
            }

            /**
             *
             * @param e1
             * @param e2
             * @param distanceX 在X轴上滑动的距离
             * @param distanceY 在轴上滑动的距离
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                Toast.makeText(context, "滚动", Toast.LENGTH_SHORT).show();
                /**
                 * X:要在X轴移动的距离
                 * Y:要在Y轴移动的距离
                 * */
                scrollBy((int) distanceX, 0);
                return true;
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                Toast.makeText(context, "双击", Toast.LENGTH_SHORT).show();
                return super.onDoubleTap(e);
            }
        });
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //遍历孩子,给每个孩子指定在屏幕上的坐标位置
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            childView.layout(i * getWidth(), 0, (i + 1) * getWidth(), getHeight());

        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        for(int i = 0;i<getChildCount();i++){
            View child = getChildAt(i);
            child.measure(widthMeasureSpec,heightMeasureSpec);

        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

    }

    private float startX;
    private float enddX;
    private int currentIndex;


    private float downX;
    private float downY;

    /**
     * 如果当前方法返回true,拦截事件,将会触发当前控件的onTouchEvent
     * 如果当前返回false,不拦截事件,事件继续分发
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        super.onInterceptTouchEvent(ev);
        detector.onTouchEvent(ev);
        boolean result = false;//默认传递给孩子
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.w("www","onInterceptTouchEvent == ACTION_DOWN");
                //1.记录坐标
                downX = ev.getX();
                downY = ev.getY();

                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www","onInterceptTouchEvent == ACTION_MOVE");
                //2.记录结束值
                float endX = ev.getX();
                float endY = ev.getY();

                //3.计算绝对值
                float distanceX = Math.abs(endX - downX);
                float distanceY = Math.abs(endY - downY);

                if(distanceX > distanceY && distanceX > 5 ){
                    result = true;
                }

                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        //3.把事件传递给手势识别器
        detector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.w("www","onTouchEvent == ACTION_DOWN");
                //1.记录坐标
                startX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                Log.w("www","onTouchEvent == ACTION_MOVE");

                break;
            case MotionEvent.ACTION_UP:
                Log.w("www","onTouchEvent == ACTION_UP");
                //2.来到新的坐标
                enddX = event.getX();
                //下标位置
                int tempIndex = currentIndex;
                if ((startX - enddX) > getWidth() / 2) {
                    //显示下一个页面
                    tempIndex++;
                } else if ((enddX - startX) > getWidth() / 2) {
                    //显示上一个页面
                    tempIndex--;
                }
                //根据下标位置移动到指定页面
                scrollToPager(tempIndex);

                break;
        }
        return true;

    }

    /**
     * 屏蔽非法值,根据下标位置移动到指定页面
     *
     * @param tempIndex
     */
    public void scrollToPager(int tempIndex) {
        if (tempIndex < 0) {
            tempIndex = 0;

        } else if (tempIndex > getChildCount() - 1) {
            tempIndex = getChildCount() - 1;
        }
        //当前变量的下标位置
        currentIndex = tempIndex;

        if(mOnPagerChangListener != null){
            mOnPagerChangListener.onScrollToPager(currentIndex);
        }

        //移动到指定位置
     //   scrollTo(currentIndex*getWidth(),0);
//        scroller = new MyScroller();
        //总距离
        int distanceX = currentIndex * getWidth() - getScrollX();
        scroller.startScroll(getScrollX(), getScrollY(), distanceX, 0);

        //刷新
        invalidate();//导致onDraw(),computerScroll()执行


    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(scroller.computerScrollOffset()){
            float currX= scroller.getCurrX();
            scrollTo((int)currX,0);
            invalidate();
    }

}

    //1.定义接口
    //设置监听页面的改变
    public interface OnPagerChangListener{
        /**
         * 当页面改变的时候回调这个方法
         * @param position 当前页面的下标
         * @return
         */
        void onScrollToPager(int position);
    }

    private OnPagerChangListener mOnPagerChangListener;

    /**
     * 设置页面改变的监听
     * @param l
     */
    public void setOnPagerChangListener(OnPagerChangListener l){
        mOnPagerChangListener = l;
    }

}

MyScroller

public class MyScroller {
    private float startX;//X轴的起始位置
    private float startY;//Y轴的起始位置
    private int distanceX;//在X轴上要移动的距离
    private int distanceY;//在Y轴上要移动的距离
    private long startTime;//开始的时间
    private boolean isFinish;//true移动结束
    private  long totalTime=300;//总时间

    public float getCurrX() {
        return currX;
    }

    //得到坐标
    public void setCurrX(float currX) {
        this.currX = currX;
    }

    public float currX;

    public void startScroll(float startX, float startY, int distanceX, int distanceY){
        this.startX=startX;
        this.startY=startY;
        this.distanceX=distanceX;
        this.distanceY=distanceY;
        this.startTime= SystemClock.uptimeMillis();//系统开机时间
        this.isFinish=false;
    }

    /**
     * 速度
     * 求移动一小段的距离
     * 求移动一小段对应的坐标
     * 求移动一小段对应的时间
     * true:正在移动
     * false:移动结束
     * @return
     */
       public  boolean computerScrollOffset(){
        if(isFinish){
            return  false;
        }

        //这一小段结束的时间
        long endTime = SystemClock.uptimeMillis();

        //移动一小段所花的时间
        long passTime = endTime - startTime;
        if(passTime < totalTime){
            //还没有移动结束
            //计算平均速度
//            float voleCity = distanceX / totalTime;
            //移动这一小段对应的距离
            float distanceSmallX = passTime * distanceX / totalTime ;

            //移动这一小段后对应的坐标
            currX = startX + distanceSmallX;  //起始位置+移动的距离
        }else{
            //移动结束
            isFinish = true;
            currX = startX + distanceX; //移动结束。起始位置+移动的距离(此时达到最大)
        }
        return  true;
    }
}

MainActivity

public class MainActivity extends Activity {
    private MyViewPager MyViewPager;
    int ids[]={R.drawable.a1,R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a5,R.drawable.a6};

    private RadioGroup radio;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewPager = (MyViewPager) findViewById(R.id.MyViewPager);
        radio = (RadioGroup) findViewById(R.id.radio);

        //添加页面
        for (int i = 0; i < ids.length; i++) {
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(ids[i]);

            //添加到MyViewPager这个View中
            MyViewPager.addView(imageView);
        }

        //添加test.xml页面
        View testView = View.inflate(this,R.layout.test,null);
        MyViewPager.addView(testView,2);

        for (int i = 0; i < MyViewPager.getChildCount(); i++) {
            RadioButton button = new RadioButton(this);
            button.setId(i);
            //添加到radiogroup
            radio.addView(button);

            //默认选择第一个页面
            radio.check(0);

            //设置radiogroup选中状态的变化
            radio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                /**
                 * @param group
                 * @param checkedId 0~5之间
                 */
                @Override
                public void onCheckedChanged(RadioGroup group, int checkedId) {
                    //根据下标位置定位到具体每个页面
                    MyViewPager.scrollToPager(checkedId);
                }
            });

            //设置监听页面的改变
            MyViewPager.setOnPagerChangListener(new MyViewPager.OnPagerChangListener() {
                /**
                 * 0~5
                 *
                 * @param position 当前页面的下标
                 * @return
                 */
                @Override
                public void onScrollToPager(int position) {
                    radio.check(position);
                }

            });


        }
    }


}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.example.viewpager2.MainActivity"
    android:orientation="vertical"
    >

    <com.example.viewpager2.MyViewPager
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:id="@+id/MyViewPager"
        android:layout_below="@id/radio"

/>

    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_horizontal"
        android:layout_gravity="center_horizontal"
        android:id="@+id/radio"
        >

    </RadioGroup>
</LinearLayout>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值