美文网首页android自定义view
自定义ViewGroup实现轮播图效果

自定义ViewGroup实现轮播图效果

作者: 刘孙猫咪 | 来源:发表于2019-04-07 17:08 被阅读105次

    对应轮播图的效果在app中随处可见,实现的方式也比较多,也有很多第三方好用的开源库,比如banner轮播图,当然了,如果不想使用第三方或者第三方在某种程度上不能满足开发需要时,自己实现也并不是太复杂,多半会采用Viewpager、Handler等方式来实现轮播图的效果,这里并没有采用该种方式,而是采用自定义ViewGroup、Handler、Timer等方式来实现的。

    GIF.gif
    在自定义ViewGroup时需要注意容器的宽度测量,应该根据子view测量的宽度*子view的总数;
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取子view的数量
            children = getChildCount();
            if (0 == children) {
                setMeasuredDimension(0, 0);
            } else {
                //测量子视图的宽度和高度
                measureChildren(widthMeasureSpec, heightMeasureSpec);
                View view = getChildAt(0);
                childWidth = view.getMeasuredWidth();
                childHeight = view.getMeasuredHeight();
                //所有子视图宽度的总和
                int width = view.getMeasuredWidth() * children;
                setMeasuredDimension(width, childHeight);
            }
        }
    

    测量出录播图的大小后,就需要在onLayout对显示轮播图的ImageView进行摆放,在摆放时对于top就是0,bottom就是录播图显示的高度,需要动态改变的就是它显示的left和right,第一个ImageView的left就是0,后面的话就是上一个ImageView的right,right就是left和轮播图宽度的和;

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (changed) {
                int leftMargin = 0;
                for (int i = 0; i < children; i++) {
                    View view = getChildAt(i);
                    view.layout(leftMargin, 0, leftMargin + childWidth, childHeight);
                    leftMargin += childWidth;
                }
            }
    
        }
    

    测量和摆放好后,通过scrollTo方法和Scroller类中的startScroll方法进行滑动,在刚创建完毕需要computeScroll方法中将它移动到第一个显示;

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

    然后在onInterceptTouchEvent和onTouchEvent方法中处理手势;

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return true;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    //按下后停止轮播
                    stopAuto();
                    if (!scroller.isFinished()) {
                        //如果没有完成 结束滑动过程
                        scroller.abortAnimation();
                    }
                    isClick = true;
                    x = event.getX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float moveX = event.getX();
                    float distance = moveX - x;
                    if (Math.abs(distance) > 10) {
                        isClick = false;
                    }
                    scrollBy((int) -distance, 0);
                    x = moveX;
                    break;
                case MotionEvent.ACTION_UP:
                    int scrollX = getScrollX();
                    index = (scrollX + childWidth / 2) / childWidth;
                    if (index < 0) {//已经滑动到最左边那张图片
                        index = 0;
                    } else if (index > children - 1) {
                        index = children - 1;//滑动到最右边的图片
                    }
                    if (isClick) {
                        //点击事件
                        if (listener != null) {
                            listener.clickImageIndex(index);
                        }
                    } else {
    
                    }
                    //计算滑动的距离
                    int dx = index * childWidth - scrollX;
    //                scrollTo(index * childWidth, 0);
                    scroller.startScroll(scrollX, 0, dx, 0, 500);
                    postInvalidate();
                    //通知指示器改变
                    if (pointListener != null) {
                        pointListener.selectImage(index);
                    }
                    //当用户松开时又重新开启图片轮播
                    startAuto();
                    break;
            }
            return true;
        }
    

    轮播图的点击和指示器的改变都是在onTouchEvent方法中,然后通过接口回调的方式来实现的,接下来就是自动轮播的实现了,采用Handler、Timer、TimerTask的方式来实现的自动轮播,实例化一个TimerTask,每隔一段时间由Timer来执行,然后在Handler中去处理轮播;

    /**
         * 初始化话Scroller TimerTask 实现自动轮播
         */
        private void initObj() {
            scroller = new Scroller(getContext());
            task = new TimerTask() {
                @Override
                public void run() {
                    if (isAuto) {
                        //开启轮播图
                        autoHandler.sendEmptyMessage(0);
                    }
                }
            };
            timer.schedule(task, 100, 2500);
        }
    
    private Handler autoHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 0:
                        int duration = 1000;
                        //实现图片的自动轮播
                        if (++index >= children) {
                            //最后一张图片  从第一张图片开始重新滑动
                            index = 0;
                            duration = 1;
                        }
                        int scrollX = getScrollX();
                        int dx = index * childWidth - scrollX;
                        scroller.startScroll(scrollX, 0, dx, 0, duration);
                        postInvalidate();
                        //通知指示器改变
                        if (pointListener != null) {
                            pointListener.selectImage(index);
                        }
                        break;
                }
            }
        };
    

    在定义ViewGroup中并没有具体的轮播图显示ImageView的创建,而是在另外一个自定容器中来实现,将轮播图和指示器放在另外一个自定义容器中,在其构造方法中就对之前写好的轮播图和指示器容器进行初始化;

    /**
         * 加载图片轮播
         */
        private void initImageBannerViewGroup() {
            imageBarnnerViewGroup = new ImageBarnnerViewGroup(getContext());
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            imageBarnnerViewGroup.setLayoutParams(lp);
            addView(imageBarnnerViewGroup);
            //设置轮播图切换监听
            imageBarnnerViewGroup.setPointSelectListener(new ImageBarnnerViewGroup.ImageBannerViewGroupListener() {
                @Override
                public void selectImage(int position) {
                    int count = linearLayout.getChildCount();
                    for (int i = 0; i < count; i++) {
                        ImageView childAt = (ImageView) linearLayout.getChildAt(i);
                        if (position == i) {
                            childAt.setImageResource(R.drawable.dot_select);
                        } else {
                            childAt.setImageResource(R.drawable.dot_normal);
                        }
                    }
                }
            });
            //设置点击轮播图点击事件监听
            imageBarnnerViewGroup.setOnImageBannerListener(new ImageBarnnerViewGroup.ImageBannerListener() {
                @Override
                public void clickImageIndex(int position) {
                    if (listener != null) {
                        listener.clickImageIndex(position);
                    }
                }
            });
        }
    
    /**
         * 加载底部指示器
         */
        private void initDotLinearLayout() {
            linearLayout = new LinearLayout(getContext());
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, dip2px(40));
            linearLayout.setLayoutParams(lp);
            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
            linearLayout.setGravity(Gravity.CENTER);
            addView(linearLayout);
    
            FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
            layoutParams.gravity = Gravity.BOTTOM;
            linearLayout.setLayoutParams(layoutParams);
    
        }
    

    在使用时根据调用addPoint方法传入的参数进行ImageView和指示器view的创建;

    /**
         * 调用该方法设置轮播图的数量
         * @param list 传入轮播的数据源
         */
        public void addPoint(List<Integer> list) {
            for (Integer integer : list) {
                addBitmapToImageBannerViewGroup(integer);
                addDotToLinearLayout();
            }
        }
    
        /**
         * 添加轮播的指示器
         */
        private void addDotToLinearLayout() {
            ImageView imageView = new ImageView(getContext());
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            lp.rightMargin = dip2px(5);
            lp.leftMargin = dip2px(5);
            imageView.setLayoutParams(lp);
            imageView.setImageResource(R.drawable.dot_normal);
            linearLayout.addView(imageView);
        }
    
        /**
         * 添加轮播的图片
         * @param resId
         */
        private void addBitmapToImageBannerViewGroup(int resId) {
            ImageView iv = new ImageView(getContext());
            iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(screenWidth, ViewGroup.LayoutParams.MATCH_PARENT);
            iv.setImageResource(resId);
            iv.setLayoutParams(lp);
            imageBarnnerViewGroup.addView(iv);
        }
    

    具体代码的实现都是在自定义ViewGroup和自定义容器中,外部通过addPoint、startAuto、stopAuto、destoryAuto等方法进行轮播图的创建、开启自动轮播、暂停自动轮播、销毁轮播图等操作;

    public class MainActivity extends AppCompatActivity {
        private List<Integer> ids;
        private ImageBannerFramLayout bannerLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            bannerLayout = findViewById(R.id.banner_layout);
            ids = new ArrayList<>();
            ids.add(R.mipmap.banner);
            ids.add(R.mipmap.baner01);
            ids.add(R.mipmap.banner02);
            ids.add(R.mipmap.baner04);
            ids.add(R.mipmap.banner05);
            ids.add(R.mipmap.banner06);
            bannerLayout.addPoint(ids);
    
            bannerLayout.setOnImageBannerListener(new ImageBannerFramLayout.ImageBannerListener() {
                @Override
                public void clickImageIndex(int position) {
                    Toast.makeText(MainActivity.this, "点击了" + position, Toast.LENGTH_LONG).show();
                }
            });
        }
    
    
        @Override
        protected void onStop() {
            super.onStop();
            bannerLayout.stopAuto();
        }
    
        @Override
        protected void onRestart() {
            super.onRestart();
            bannerLayout.startAuto();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            bannerLayout.destoryAuto();
        }
    }
    

    完整代码:

    /**
     * 自定义轮播图效果
     */
    public class ImageBarnnerViewGroup extends ViewGroup {
        //获取子view的数量
        private int children;
        //子视图宽度
        private int childWidth;
        //子视图高度
        private int childHeight;
        //第一次按下的x位置  每一次移动过程中 移动之前的位置左边
        private float x;
        //每张图片的索引
        private int index = 0;
        private Scroller scroller;
        //是否自动轮播  默认情况下开启自动轮播
        private boolean isAuto = true;
        private Timer timer = new Timer();
        private TimerTask task;
        private Handler autoHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 0:
                        int duration = 1000;
                        //实现图片的自动轮播
                        if (++index >= children) {
                            //最后一张图片  从第一张图片开始重新滑动
                            index = 0;
                            duration = 1;
                        }
                        int scrollX = getScrollX();
                        int dx = index * childWidth - scrollX;
                        scroller.startScroll(scrollX, 0, dx, 0, duration);
                        postInvalidate();
                        //通知指示器改变
                        if (pointListener != null) {
                            pointListener.selectImage(index);
                        }
                        break;
                }
            }
        };
        private ImageBannerListener listener;
        private ImageBannerViewGroupListener pointListener;
        //是否是点击事件
        private boolean isClick = false;
    
        /**
         * 开始播放轮播图
         */
        public void startAuto() {
            isAuto = true;
        }
    
        /**
         * 暂停播放轮播图
         */
        public void stopAuto() {
            isAuto = false;
        }
    
        /**
         * 当页面销毁的时候调用该方法
         */
        public void destoryAuto() {
            isAuto = false;
            if (autoHandler != null) {
                //移除handler消息
                autoHandler.removeCallbacksAndMessages(null);
            }
            if (task != null) {
                task.cancel();
                task = null;
            }
            if (timer != null) {
                timer.cancel();
                timer = null;
            }
        }
    
        public ImageBarnnerViewGroup(Context context) {
            this(context, null);
        }
    
        public ImageBarnnerViewGroup(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public ImageBarnnerViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initObj();
        }
    
        /**
         * 初始化话Scroller TimerTask 实现自动轮播
         */
        private void initObj() {
            scroller = new Scroller(getContext());
            task = new TimerTask() {
                @Override
                public void run() {
                    if (isAuto) {
                        //开启轮播图
                        autoHandler.sendEmptyMessage(0);
                    }
                }
            };
            timer.schedule(task, 100, 2500);
        }
    
        @Override
        public void computeScroll() {
            super.computeScroll();
            if (scroller.computeScrollOffset()) {
                scrollTo(scroller.getCurrX(), 0);
                invalidate();
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取子view的数量
            children = getChildCount();
            if (0 == children) {
                setMeasuredDimension(0, 0);
            } else {
                //测量子视图的宽度和高度
                measureChildren(widthMeasureSpec, heightMeasureSpec);
                View view = getChildAt(0);
                childWidth = view.getMeasuredWidth();
                childHeight = view.getMeasuredHeight();
                //所有子视图宽度的总和
                int width = view.getMeasuredWidth() * children;
                setMeasuredDimension(width, childHeight);
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (changed) {
                int leftMargin = 0;
                for (int i = 0; i < children; i++) {
                    View view = getChildAt(i);
                    view.layout(leftMargin, 0, leftMargin + childWidth, childHeight);
                    leftMargin += childWidth;
                }
            }
    
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return true;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    //按下后停止轮播
                    stopAuto();
                    if (!scroller.isFinished()) {
                        //如果没有完成 结束滑动过程
                        scroller.abortAnimation();
                    }
                    isClick = true;
                    x = event.getX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float moveX = event.getX();
                    float distance = moveX - x;
                    if (Math.abs(distance) > 10) {
                        isClick = false;
                    }
                    scrollBy((int) -distance, 0);
                    x = moveX;
                    break;
                case MotionEvent.ACTION_UP:
                    int scrollX = getScrollX();
                    index = (scrollX + childWidth / 2) / childWidth;
                    if (index < 0) {//已经滑动到最左边那张图片
                        index = 0;
                    } else if (index > children - 1) {
                        index = children - 1;//滑动到最右边的图片
                    }
                    if (isClick) {
                        //点击事件
                        if (listener != null) {
                            listener.clickImageIndex(index);
                        }
                    } else {
    
                    }
                    //计算滑动的距离
                    int dx = index * childWidth - scrollX;
    //                scrollTo(index * childWidth, 0);
                    scroller.startScroll(scrollX, 0, dx, 0, 500);
                    postInvalidate();
                    //通知指示器改变
                    if (pointListener != null) {
                        pointListener.selectImage(index);
                    }
                    //当用户松开时又重新开启图片轮播
                    startAuto();
                    break;
            }
            return true;
        }
        /**
         * 设置轮播图点击事件监听
         *
         * @param listener
         */
        public void setOnImageBannerListener(ImageBannerListener listener) {
            this.listener = listener;
        }
    
        /**
         * 轮播图点击接口
         */
        public interface ImageBannerListener {
            void clickImageIndex(int position);
        }
    
        /**
         * 轮播图滑动监听
         */
        public interface ImageBannerViewGroupListener {
            void selectImage(int position);
        }
    
        /**
         * 设置轮播图滑动监听
         *
         * @param listener
         */
        public void setPointSelectListener(ImageBannerViewGroupListener listener) {
            this.pointListener = listener;
        }
    }
    
    
    /**
     * 自定义轮播图
     */
    public class ImageBannerFramLayout extends FrameLayout {
        //图片轮播
        private ImageBarnnerViewGroup imageBarnnerViewGroup;
        //屏幕的宽度
        private int screenWidth;
        //底部指示器容器
        private LinearLayout linearLayout;
        //点击监听回调接口
        private ImageBannerListener listener;
    
        public ImageBannerFramLayout(Context context) {
            this(context, null);
        }
    
        public ImageBannerFramLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public ImageBannerFramLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            screenWidth = getScreenWidth();
            initImageBannerViewGroup();
            initDotLinearLayout();
        }
    
        /**
         * 加载图片轮播
         */
        private void initImageBannerViewGroup() {
            imageBarnnerViewGroup = new ImageBarnnerViewGroup(getContext());
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            imageBarnnerViewGroup.setLayoutParams(lp);
            addView(imageBarnnerViewGroup);
            //设置轮播图切换监听
            imageBarnnerViewGroup.setPointSelectListener(new ImageBarnnerViewGroup.ImageBannerViewGroupListener() {
                @Override
                public void selectImage(int position) {
                    int count = linearLayout.getChildCount();
                    for (int i = 0; i < count; i++) {
                        ImageView childAt = (ImageView) linearLayout.getChildAt(i);
                        if (position == i) {
                            childAt.setImageResource(R.drawable.dot_select);
                        } else {
                            childAt.setImageResource(R.drawable.dot_normal);
                        }
                    }
                }
            });
            //设置点击轮播图点击事件监听
            imageBarnnerViewGroup.setOnImageBannerListener(new ImageBarnnerViewGroup.ImageBannerListener() {
                @Override
                public void clickImageIndex(int position) {
                    if (listener != null) {
                        listener.clickImageIndex(position);
                    }
                }
            });
        }
    
        /**
         * 加载底部指示器
         */
        private void initDotLinearLayout() {
            linearLayout = new LinearLayout(getContext());
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, dip2px(40));
            linearLayout.setLayoutParams(lp);
            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
            linearLayout.setGravity(Gravity.CENTER);
            addView(linearLayout);
    
            FrameLayout.LayoutParams layoutParams = (LayoutParams) linearLayout.getLayoutParams();
            layoutParams.gravity = Gravity.BOTTOM;
            linearLayout.setLayoutParams(layoutParams);
    
        }
    
        /**
         * 调用该方法设置轮播图的数量
         * @param list 传入轮播的数据源
         */
        public void addPoint(List<Integer> list) {
            for (Integer integer : list) {
                addBitmapToImageBannerViewGroup(integer);
                addDotToLinearLayout();
            }
        }
    
        /**
         * 添加轮播的指示器
         */
        private void addDotToLinearLayout() {
            ImageView imageView = new ImageView(getContext());
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            lp.rightMargin = dip2px(5);
            lp.leftMargin = dip2px(5);
            imageView.setLayoutParams(lp);
            imageView.setImageResource(R.drawable.dot_normal);
            linearLayout.addView(imageView);
        }
    
        /**
         * 添加轮播的图片
         * @param resId
         */
        private void addBitmapToImageBannerViewGroup(int resId) {
            ImageView iv = new ImageView(getContext());
            iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(screenWidth, ViewGroup.LayoutParams.MATCH_PARENT);
            iv.setImageResource(resId);
            iv.setLayoutParams(lp);
            imageBarnnerViewGroup.addView(iv);
        }
    
        private int dip2px(int dip) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
        }
    
        /**
         * 获取手机屏幕的宽度
         *
         * @return
         */
        private int getScreenWidth() {
            WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics displayMetrics = new DisplayMetrics();
            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
            return displayMetrics.widthPixels;
        }
    
        public void setOnImageBannerListener(ImageBannerListener listener) {
            this.listener = listener;
        }
    
        public interface ImageBannerListener {
            void clickImageIndex(int position);
        }
        /**
         * 开始播放轮播图
         */
        public void startAuto(){
            imageBarnnerViewGroup.startAuto();
        }
    
        /**
         * 暂停播放轮播图
         */
        public void stopAuto(){
            imageBarnnerViewGroup.stopAuto();
        }
    
        /**
         * 当页面销毁的时候调用该方法
         */
        public void destoryAuto(){
            imageBarnnerViewGroup.destoryAuto();
        }
    }
    
    

    源码

    相关文章

      网友评论

        本文标题:自定义ViewGroup实现轮播图效果

        本文链接:https://www.haomeiwen.com/subject/epanbqtx.html