android自定义view

自定义ViewGroup实现轮播图效果

2019-04-07  本文已影响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();
    }
}

源码

上一篇 下一篇

猜你喜欢

热点阅读