优雅自定义轮播图

来源:互联网 发布:软件无线电技术与实现 编辑:程序博客网 时间:2024/05/24 06:05

开篇

这个是好久前项目中自己写的,实现了一组图片的轮播展示效果,解脱第三方,打造属于自己的轮播组件。

效果预期

这里写图片描述

概要设计轮播容器及注意事项

1.BannerView只要有两部分部分组成,一部分是ViewPager,另一部分是指向器。那么我们只需要向容器添加ViewPager和特定的指向器容器即可,然后将二者建立连接,达到我们轮播图说要的效果。

2.BannerView一般都需要做得到尺寸的适配,为了优化我们BannerView的性能,我们可以修改我们的onMeasure()方法和onLayout()方法,通过等比例缩修改我们BannerView的尺寸来做到任何机型都能完美显示

3.BannerView在通过Handler来处理动态切换的时候,我们需要预防内训泄露,在Activity的onResume()中开启我们的播放,在onStoop()中停止我们的播放。否则很有可能会造成内存泄露,切记。

编码阶段

(一).构造方法,这里我们将从xml文件中得到我们自定义的属性,当然这些属性我们也可以在java代码中动态设置。

 public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        //拿到自定义属性的容器        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.BannerView);        //指向器的半径        point_size = (int) array.getDimension(R.styleable.BannerView_point_size, 15);        //指向器的颜色        //point_bg = array.getColor(R.styleable.BannerView_point_bg, 0x00000000);        point_bg = Color.parseColor("#00000000");        //指向器是否在BannerView的右侧(居中和右侧两种情况)        isRight = array.getBoolean(R.styleable.BannerView_is_right, false);        //是否需要自动播放        isPlay = array.getBoolean(R.styleable.BannerView_is_play, true);        //当没有数据的时候,是否需要提供一张默认图,否则我们将会隐藏掉        isNeedStaticLocaltion = array.getBoolean(R.styleable.BannerView_is_static_location, true);        //指向器未选中的颜色        nomalColor = array.getColor(R.styleable.BannerView_point_color_unselect, Color.WHITE);        //指向器选中的颜色        selectColor = array.getColor(R.styleable.BannerView_point_color_select, Color.BLACK);        //BannerView的高:宽的比例        proportion = array.getFloat(R.styleable.BannerView_banner_proportion, 0.6f);        //回收资源        array.recycle();    }

(二).BannerView的初始化入口是setConfig()方法,这个方法在随着外界用户提供的数据会向BannerView中添加ViewPager和指向器容器,我们慢慢看来。

public void setConfig(List<T> pictureList, boolean needStaticLocaltion, boolean isPlay, UserControal<T> userControal) {        //这里一定要先移除所有的View,有可能会外界用户会调用多次的setConfig方法        removeAllViews();        this.pictureList = pictureList;        isNeedStaticLocaltion = needStaticLocaltion;        this.isPlay = isPlay;        this.userControal = userControal;        //初始化BannerView        init();        //初始化指向器        initPoint();        //初始化vp        initVp();        //最后会指向autoPlay        autoPlay();    }

1.初始化BannerView

/**     * 添加ViewPager和指向器布局     */    private void init() {        vp = new ViewPager(getContext());        pointLayout = new RelativeLayout(getContext());        addView(vp);        addView(pointLayout);        RelativeLayout.LayoutParams lpVp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);        RelativeLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);        lp.rightMargin = 38;        pointLayout.setBackgroundColor(point_bg);        vp.setLayoutParams(lpVp);        pointLayout.setLayoutParams(lp);    }

2.初始化指向器

 /**     * 生成圆点指示器     */    public void initPoint() {        if (null == pictureList || pictureList.size() < 2) {            return;        }        LinearLayout layout = new LinearLayout(getContext());        int size = pictureList.size();        layout.setOrientation(LinearLayout.HORIZONTAL);        myCilcleViews = new ArrayList<>();        for (int i = 0; i < size; i++) {            MyCilcleView cilcleView = new MyCilcleView(getContext());            layout.addView(cilcleView);            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(point_size, point_size);            lp.setMargins(MARGIN, 0, MARGIN, 0);            cilcleView.setLayoutParams(lp);            if (i == 0) {                cilcleView.drawPoint(selectColor, point_size);            } else {                cilcleView.drawPoint(nomalColor, point_size);            }            myCilcleViews.add(cilcleView);        }        RelativeLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);        pointLayout.addView(layout, lp);    }

3.圆点View

/**     * point,画出我们想要的实行园     */    public class MyCilcleView extends TextView {        private int mColor = Color.BLACK;        private int mRadius = 7;        public MyCilcleView(Context context) {            super(context);        }        public MyCilcleView(Context context, AttributeSet attrs) {            super(context, attrs);        }        public MyCilcleView(Context context, AttributeSet attrs, int defStyleAttr) {            super(context, attrs, defStyleAttr);        }        @Override        protected void onDraw(Canvas canvas) {            super.onDraw(canvas);            Paint p = new Paint();            p.setColor(mColor);            //画笔是否实心            p.setStyle(Paint.Style.FILL);            //抗锯齿            p.setAntiAlias(true);            int mineRadius = Math.min(getWidth() / 2, getHeight() / 2);            if (mRadius > mineRadius) {                mRadius = mineRadius;            }            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, p);        }        public void drawPoint(int mColor, int mRadius) {            this.mColor = mColor;            this.mRadius = mRadius;            invalidate();        }    }

4.初始化ViewPager,大致思路就是根据外界传递的数据动态添加图片,值得注意的一点就是,我们当滑动到最后一张时,如何平稳的滑动到第一张,那么我们可以尝试这么做,如果我们需要展示5张图片,那么我们需要添加6个ImageView,第六个和第一个展示的图片是一样的,当滑动到底六张时我们就让当前的currentPosition为0,这样就解决了由最后一张滑动到第一张跳动的bug。当然还要处理我们触摸时间,当我们触摸在ViewPager上的时候我们的ViewPager是不可以自动播放的。

 private void initVp() {        imageViews = new ArrayList<>();        if (pictureList == null || pictureList.size() == 0) {            if (isNeedStaticLocaltion) {                ImageView img = new ImageView(getContext());                img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));                img.setImageResource(mDefaultImg);                img.setScaleType(ImageView.ScaleType.CENTER_CROP);                imageViews.add(img);                initImg(0);            } else {                setVisibility(GONE);                return;            }        } else {            int size = pictureList == null ? 0 : pictureList.size() + 1;            if (pictureList != null && pictureList.size() == 1) {                size = 1;            }            for (int i = 0; i < size; i++) {                ImageView img = new ImageView(getContext());                img.setImageResource(mDefaultImg);                img.setScaleType(ImageView.ScaleType.CENTER_CROP);                img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));                imageViews.add(img);                initImg(i);            }        }        BannerAdapter adapter = new BannerAdapter();        vp.setAdapter(adapter);        vp.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {            @Override            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {            }            @Override            public void onPageSelected(int position) {                if (pictureList == null || pictureList.size() <= 1) {                    return;                }                if (position == pictureList.size()) {                    vp.setCurrentItem(0, false);                    myCilcleViews.get(0).drawPoint(selectColor, point_size);                    myCilcleViews.get(position - 1).drawPoint(nomalColor, point_size);                    currentPosition = 0;                    return;                }                myCilcleViews.get(currentPosition).drawPoint(nomalColor, point_size);                myCilcleViews.get(position).drawPoint(selectColor, point_size);                currentPosition = position;            }            @Override            public void onPageScrollStateChanged(int state) {            }        });        vp.setOnTouchListener(new OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                switch (event.getAction()) {                    case MotionEvent.ACTION_DOWN:                        currentX = event.getX();                        currentY = event.getY();                        stop();                        break;                    case MotionEvent.ACTION_MOVE:                        break;                    case MotionEvent.ACTION_UP:                        if (Math.max(Math.abs(currentX - event.getX()), Math.abs(currentY - event.getY())) > MINE_XY) {                            //属于滑动了                            autoPlay();                        } else {                            if (userControal != null) {                                userControal.pageClick(currentPosition, t);                            }                        }                        break;                }                return false;            }        });    }

5.自动轮播

 /**     * 暴露的方法,开始轮播     */    public void autoPlay() {        if (pictureList == null) {            return;        }        if (!isPlay && pictureList.size() > 1) {            return;        }        hander.sendEmptyMessageDelayed(FLAG, 3000);    }
 private Handler hander = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            if (null != msg) {                if (msg.what == FLAG) {                //移除掉消息                    hander.removeMessages(FLAG);                    vp.setCurrentItem(currentPosition + 1);                    hander.sendEmptyMessageDelayed(FLAG, 3000);                }            }        }    };

6.更新BannerView组件

  public void notifyBanner(List<T> pictureList) {        removeAllViews();        this.pictureList = pictureList;        init();        initPoint();        initVp();        autoPlay();    }

7.测量和layout

//这里我们将根据proportion,来得到我们的高度,然后makeMeasureSpec()方法来生成我们适配的测量数据,最后调用super方法,我们的model设置为MeasureSpec.EXACTLY,这样提升了我们的性能,然后又不会出错。 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int w = MeasureSpec.getSize(widthMeasureSpec);        int h = (int) (w * proportion);        int count = getChildCount();        for (int i = 0; i < count; i++) {            View child = getChildAt(i);            if (i == 0 && child instanceof ViewPager) {                int hM = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);                child.measure(widthMeasureSpec, hM);            } else {                int hM = MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST);                child.measure(widthMeasureSpec, hM);            }        }        //setMeasuredDimension(w, h);        int MW = MeasureSpec.makeMeasureSpec(w,MeasureSpec.EXACTLY);        int MH =  MeasureSpec.makeMeasureSpec(h,MeasureSpec.EXACTLY);        super.onMeasure(MW, MH);    }
//这里指的注意的是,我们的指向器是有两中选择的,这里就需要我们的onLayout方法来完成这个效果,把指向器摆放在指定的位置。 @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        int count = getChildCount();        for (int i = 0; i < count; i++) {            View v = getChildAt(i);            if (v == vp) {                v.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());            }            if (v == pointLayout) {                pointLayout.layout(0, getMeasuredHeight() - pointLayout.getMeasuredHeight() - 30, getMeasuredWidth(), getMeasuredHeight());                int c = pointLayout.getChildCount();                if (c == 1) {                    View view = pointLayout.getChildAt(0);                    int w = pointLayout.getMeasuredWidth();                    int ww = view.getMeasuredWidth();                    int centerW = (w - ww) / 2;                    int h = pointLayout.getMeasuredHeight();                    int hh = view.getMeasuredHeight();                    int centerH = (h - hh) / 2;                    if (!isRight) {                        view.layout(centerW, centerH, centerW + ww, centerH + hh);                    } else {                        view.layout(w - ww - 30, centerH, w - 30, centerH + hh);                    }                }            }        }    }

8.回调接口

    public interface UserControal<T> {        void initPicture(ImageView img, T t, int position);        void pageClick(int position, T t);    }    //这个跟回调关系巨大,我们将图片的加载交由用户处理       private void initImg(int position) {        if (pictureList == null || pictureList.size() == 0) {            imageViews.get(position).setImageResource(mDefaultImg);            return;        }        if (userControal != null) {            if (position < pictureList.size()) {                //imageViews的个数比pircture的数量多1                userControal.initPicture(imageViews.get(position), pictureList.get(position), position);            } else {                userControal.initPicture(imageViews.get(position), pictureList.get(0), 0);            }        }    }

总结

这里,就是大部分源码了,优雅实现了适配任何机型的轮播View,由于特殊原因无法上传github,有需要的说一声。

1 0