自定义ViewGroup实现循环轮播ViewPager

重复造轮子有利于理解和发明。自定义实现了一个轮播ViewPager。代码如下

package com.cc.codetest;

import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Adapter;
import android.widget.RelativeLayout;
import android.widget.Scroller;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by cc on 2017/a3/a2.
 */

public class MyViewPager extends ViewGroup {

    final int mCacheItem = 3;

    int mScrollTime = 800;

    int mCurrentPagPostionForAdapter;

    int mSnapVelocity = 600;

    Adapter mAdapter;

    Scroller mScroller;

    VelocityTracker mVelocityTracker;

    ViewConfiguration mViewConfiguration;

    ItemInfo[] mItemInfo = new ItemInfo[mCacheItem];

    OnPagListener mOnPagListener;

    class ItemInfo{
        View view = null;
        //offsetX 在onLayout中,决定view.layout方法的left参数
        int offsetX = -1;
    }

    interface OnPagListener{
        void onPagChange(int postion);
    }

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mScroller = new Scroller(context);
        this.mVelocityTracker = VelocityTracker.obtain();
        this.mViewConfiguration = ViewConfiguration.get(context);
    }

    /**
     * 这里子view直接使用父group的测量规格,以达到每个子View都达到填充group的大小;
     * **/
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        int maxWidth = 0, maxHeight = 0;
        for(int i = 0 ; i < childCount; i ++){
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
            if(maxWidth < getChildAt(i).getMeasuredWidth()){
                maxWidth = getChildAt(i).getMeasuredWidth();
            }
            if(maxHeight < getChildAt(i).getMeasuredHeight()){
                maxHeight = getChildAt(i).getMeasuredHeight();
            }
        }
        setMeasuredDimension(maxWidth, maxHeight);
    }

    /**
     * 布局子view,
     * isFirstLayout 是否第一次从adapter加载布局,首次加载view时,需要移动到中间的子View来提供显示。
     * **/
    boolean isFirstLayout = true;
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = getMeasuredWidth();
        View childView;
        for(int i = 0 ; i < getChildCount() ; i++){
            int offsetX = 0;
            childView = getChildAt(i);
            ItemInfo item = findItemInfo(childView);
            if(item == null){
                continue;
            }
            if(item.offsetX < 0){
                item.offsetX = i * width;
            }
            offsetX = item.offsetX;
            childView.layout(offsetX, t, offsetX + childView.getMeasuredWidth(), childView.getMeasuredHeight());
        }
        if(isFirstLayout){
            isFirstLayout = false;
            scrollTo(width * (mCacheItem / 2) + 1, getScrollY());
            mCurrentPagPostionForAdapter = 1;
        }
    }

    float mPreX = 0;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(!mScroller.isFinished()){
                    return true;
                }
                mPreX = ev.getX();
                return false;
            case MotionEvent.ACTION_MOVE:
                if(Math.abs(mPreX - ev.getX()) > mViewConfiguration.getScaledTouchSlop()){
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    float mDownLastX;
    final int mCurrentPag = 1;//当前页的下标永远是1
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(!isEnabled() || mAdapter == null || mAdapter.getCount() <= 0){
            return false;
        }
        if(!mScroller.isFinished()){
            mScroller.abortAnimation();
        }
        event = MotionEvent.obtain(event);
        mVelocityTracker.addMovement(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //记录按下时的x坐标值,用于计算拖拽时的基点
                mDownLastX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                //实现手指拖拽
                if(mDownLastX == 0){
                    mDownLastX = event.getX();
                }
                int disX = (int) (mDownLastX - event.getX());
                scrollTo(getScrollX() + disX, 0);
                mDownLastX = event.getX();
                ViewParent viewParent = getParent();
                if(viewParent != null){
                    viewParent.requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                mDownLastX = 0;
                //抬起手指后,计算出当前页的移动距离以及滑动的速度
                int scrollX = getScrollX();
                int currentPagOffSet = scrollX - mCurrentPag * getWidth();
                mVelocityTracker.computeCurrentVelocity(mViewConfiguration.getScaledMaximumFlingVelocity());
                int xVelocity = (int) mVelocityTracker.getXVelocity();
                mVelocityTracker.clear();
                //根据滑动速度,当前页移动距离,计算出将要显示的是上一页、当前页还是下一页。
                int nextPag = computeleNextPag(mCurrentPag, currentPagOffSet, xVelocity);
                if(mOnPagListener != null){
                    mOnPagListener.onPagChange(getNextPostionForAdapter(mCurrentPagPostionForAdapter));
                }
                //根据要显示的页面 进行view缓存
                checkViewCache(nextPag, currentPagOffSet);
                //重新获取x偏移,缓存view时修改了x偏移;
                scrollX = getScrollX();
                //根据剩余滑动距离,计算出对应比例时间 ,并进行滑动。
                int dx = mCurrentPag * getWidth() - scrollX;
                int newScrollTime = (int) (Math.abs(dx) * 1.00d / getWidth() * mScrollTime);
                mScroller.startScroll(scrollX, getScrollY(), dx, getScrollY(), newScrollTime);
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    public void setAdapter(Adapter adapter){
        this.mAdapter = adapter;
        if(adapter.getCount() <= 0){
            return;
        }
        for(int i = 0 ; i < mCacheItem ; i++){
            int j = i % adapter.getCount();
            View view = adapter.getView(j, null, this);
            addNewItem(i, view);
        }
        isFirstLayout = true;
        requestLayout();
    }

    void addNewItem(int i, View view){
        ItemInfo item = new ItemInfo();
        item.view = view;
        mItemInfo[i] = item;
        addView(view, i);
    }

    int computeleNextPag(int currentPag, int currentPagOffSet, int xVelocity){
        if(currentPagOffSet < 0){
            if(Math.abs(currentPagOffSet) > getWidth() / 2 || xVelocity > mSnapVelocity){
                return --currentPag;
            }else{
                return currentPag;
            }
        }else
        if(currentPagOffSet > getWidth() / 2 || -xVelocity > mSnapVelocity){
            return ++currentPag;
        }else{
            return currentPag;
        }
    }

    void checkViewCache(int nextPag, int currentPagOffSet){
        if(nextPag == 1){
            return;
        }
        if(nextPag == 2){
            mCurrentPagPostionForAdapter = getNextPostionForAdapter(mCurrentPagPostionForAdapter);
            int nextPagPostionForAdapter = getNextPostionForAdapter(mCurrentPagPostionForAdapter);
            System.arraycopy(mItemInfo, 1, mItemInfo, 0, 2);
            mItemInfo[2] = null;
            for(int i = 0 ; i < mItemInfo.length - 1; i++){
                mItemInfo[i].offsetX = i * getWidth();
            }
            View view = getChildAt(0);
            removeView(view);
            addNewItem(2, mAdapter.getView(nextPagPostionForAdapter, view, this));
            scrollTo(currentPagOffSet, getScrollY());
            return;
        }
        if(nextPag == 0){
            mCurrentPagPostionForAdapter = getPreviousPostionAdapter(mCurrentPagPostionForAdapter);
            int prevousPagPostionForAdapter = getPreviousPostionAdapter(mCurrentPagPostionForAdapter);
            System.arraycopy(mItemInfo, 0, mItemInfo, 1, 2);
            mItemInfo[0] = null;
            for(int i = 0 ; i < mItemInfo.length; i++){
                if(i != 0){
                    mItemInfo[i].offsetX = i * getWidth();
                }
            }
            View view = getChildAt(mCacheItem - 1);
            removeView(view);
            addNewItem(0, mAdapter.getView(prevousPagPostionForAdapter, view, this));
            scrollTo(getWidth() - Math.abs(currentPagOffSet) + getWidth(), getScrollY());
        }
    }

    ItemInfo findItemInfo(View view){
        for(ItemInfo info : mItemInfo){
            if(view == info.view){
                return  info;
            }
        }
        return null;
    }

    //取得对应的下一页,实现循环
    int getNextPostionForAdapter(int currentPostionForAdapter){
        currentPostionForAdapter ++;
        currentPostionForAdapter %= mAdapter.getCount();
        return currentPostionForAdapter;
    }

    //取得对应的上一页,实现循环
    int getPreviousPostionAdapter(int currentPostionForAdapter){
        currentPostionForAdapter --;
        if(currentPostionForAdapter < 0){
            currentPostionForAdapter = mAdapter.getCount() - 1;
        }
        return currentPostionForAdapter;
    }

    public void setOnPagListener(OnPagListener listener){
        this.mOnPagListener = listener;
    }

}

这样使用

        viewPager.setAdapter(new BaseAdapter() {
            @Override
            public int getCount() {
                return 3;
            }

            @Override
            public Object getItem(int position) {
                return null;
            }

            @Override
            public long getItemId(int position) {
                return 0;
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                ImageView imageView = new ImageView(getApplicationContext());
                imageView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        
                    }
                });
                switch (position){
                    case 0:
                        imageView.setImageResource(R.mipmap.a1);
                        break;
                    case 1:
                        imageView.setImageResource(R.mipmap.a2);
                        break;
                    case 2:
                        imageView.setImageResource(R.mipmap.a3);
                        break;
                }
                return imageView;
            }
        });


嗯。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android自定义ViewGroup是指在Android开发中,通过继承ViewGroup类来创建自定义的布局容器。自定义ViewGroup可以用于实现一些特殊的布局效果,比如侧滑菜单、滑动卡片等等。通过自定义ViewGroup,我们可以更灵活地控制子视图的布局和交互行为,以满足特定的需求。自定义ViewGroup实现主要包括重写onMeasure()方法和onLayout()方法,来测量和布局子视图。同时,我们还可以通过重写onInterceptTouchEvent()方法和onTouchEvent()方法来处理触摸事件,实现自定义的交互效果。如果你对自定义ViewGroup还不是很了解,或者正想学习如何自定义,可以参考相关的教程和文档,如引用\[1\]和引用\[2\]所提到的博客和官方文档。 #### 引用[.reference_title] - *1* [Android 手把手教您自定义ViewGroup(一)](https://blog.csdn.net/iteye_563/article/details/82601716)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [使用LayoutParams自定义安卓ViewGroup](https://blog.csdn.net/lfq88/article/details/127268493)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android自定义ViewGroup](https://blog.csdn.net/farsight2009/article/details/62046643)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值