自定义控件之轮播图

昨天写了关于RecyclerView添加头部布局的方法,一般我们的头部布局都会添加什么呢?自然是一个轮播形式的广告位。关于RecyclerView我们使用了比较简单的刷新提示,轮播图我们也要做一个简单而且实用,并且效率并不低的自定义控件!

关于自定义控件,一般实现的方式有两种。第一种是继承自View,整个界面用画笔在画布上一点点的画出来。今天的轮播图我们使用第二种方法:组合控件,把几个布局组合在一起形成一个界面,然后处理一下逻辑,就形成了“独特”的控件。

所谓组合控件,就是在一个特定的布局上面,把几个控件组合起来,这其实就跟我们平常写个xml布局是一样,只不过我们多加了一些处理的逻辑,让他可以自由的表现出需求的效果,例如轮播

我们首先要设计这个控件,大体如下:

自定义控件之轮播图_第1张图片


首先,创建一个类,继承自RelativeLayout。

public class MainBannerView extends RelativeLayout
这里有个小细节,就是我们继承的是RelativeLayout,而不是LinearLayout。在Androidstudio1.2版本,我们新建工程的时候,会默认布局为RelativeLayout,这是因为LinearLayout是树状接口,在加载的时候会纵向遍历,会降低布局加载的时间,当然这个时间可能微乎其微,但是,总之,细节决定成败。

将上图中需要的几个ArrayList定义出来


 // 用于存储描述字段
    private List<String> titleList = new ArrayList<>();
    // 获取图片的url
    private List<String> urlList = new ArrayList<>();
    // 对每个图片点击的事件监听
    private List<OnClickListener> listenerList = new ArrayList<>();

重写构造方法,并且绑定控件

public BannerView(Context context) {
        this(context, null);
    }

    public BannerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_banner, this, true);
        vp = (ViewPager) view.findViewById(R.id.vp);
        titleTv = (TextView) view.findViewById(R.id.tv_adv_title);
        ll = (LinearLayout) view.findViewById(R.id.ll_dots);
        adapter = new MyPagerAdapter();
        vp.setAdapter(adapter);

        // 设置ViewPager的滑动监听
        vp.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

接下来定义MyPagerAdapter

private class MyPagerAdapter extends PagerAdapter {

        // 将定义的ImageView都保存在imageViews中,防止多次定义
        private List<ImageView> imageViews = new ArrayList<>();

        @Override
        public int getCount() {
            return urlList.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView = null;
            if (position < imageViews.size()) {
                imageView = imageViews.get(position);
            } else {
                imageView = new ImageView(container.getContext());
                imageView.setScaleType(ImageView.ScaleType.FIT_XY);
                ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
                imageView.setLayoutParams(lp);
                imageViews.add(imageView);
            }
            // 防止多次加载,加载过之后设置一个标记Tag,不再多次加载
            if (imageView.getTag() == null) {
                imageLoader.displayImage(urlList.get(position), imageView);
                imageView.setOnClickListener(listenerList.get(position));
                imageView.setTag(position);
            }
            container.addView(imageView);
            return imageView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    }

添加图片滚动的逻辑,在init()方法中

/**
         * 循环滚动的逻辑
         */
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 设置是否停止,当退出程序后停止滚动
                if (!isStop) {
                    if (!isTouched) {
                        if (vp.getCurrentItem() < adapter.getCount() - 1) {
                            vp.setCurrentItem(vp.getCurrentItem() + 1);
                        } else if (vp.getCurrentItem() == adapter.getCount() - 1) {
                            vp.setCurrentItem(0);
                        }
                        // 每5秒执行一次
                        handler.postDelayed(this, 5000);
                    } else {
                        isTouched = false;
                        //有点击动作5秒内停止切换
                        handler.postDelayed(this, 5000);
                    }
                }

            }
        }, 5000);

// 当有触摸操作时,不进行滑动
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        isTouched = true;
        return super.onInterceptTouchEvent(ev);
    }

好了,轮播图的逻辑我们已经写好了,添加对外的方法,来添加图片信息以及点击事件

/**
     * 当界面刷新,有新数据的时候,先清空然后重新添加item
     */
    public void clear() {
        ll.removeAllViews();
        titleList.clear();
        urlList.clear();
        listenerList.clear();
        adapter.notifyDataSetChanged();
    }

    /**
     * 对外的一个方法,用来添加滑动图片
     *
     * @param title
     * @param url
     * @param listener
     */
    public void addItem(String title, String url, OnClickListener listener) {
        // 设置右下角的远点,每增加一个item,就往Linearlayout里面添加一个点
        View view = new View(getContext());
        view.setBackgroundResource(R.drawable.con_circle_normal);
        int size = dpToPx(8);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);
        int marginRight = dpToPx(5);
        params.setMargins(0, 0, marginRight, 0);
//        view.setLayoutParams(params);
        ll.addView(view, params);
        // 将数据添加到列表中
        titleList.add(title);
        urlList.add(url);
        listenerList.add(listener);
        // 数据加完,更新adapter
        adapter.notifyDataSetChanged();
        // 添加第一个item后就要选中第一个
        if (titleList.size() == 1) {
            titleTv.setText(titleList.get(0));
            switchDots(0);
        }
    }

    /**
     * 设置当前显示圆点
     *
     * @param index
     */
    private void switchDots(int index) {
        int count = ll.getChildCount();
        for (int i = 0; i < count; i++) {
            View view = ll.getChildAt(i);
            if (index == i) {
                view.setBackgroundResource(R.drawable.con_circle_focus);
            } else {
                view.setBackgroundResource(R.drawable.con_circle_normal);
            }
        }
    }

    public int dpToPx(int dp) {
        return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f);
    }

当程序退出,停止滚动,对外的一个方法

// 控制是否滚动
    private boolean isStop;

    public boolean isStop() {
        return isStop;
    }

    public void setIsStop(boolean isStop) {
        this.isStop = isStop;
    }

还有轮播图的布局view_banner:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v4.view.ViewPager
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/vp"/>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@drawable/advbottom_img_bg"
        android:layout_alignParentBottom="true"
        android:gravity="center_vertical"
        >

        <TextView
            android:layout_width="0px"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:id="@+id/tv_adv_title"
            android:layout_marginLeft="5dp"
            android:textSize="15sp"
            android:textColor="#ffffff"
            android:ellipsize="end"
            android:layout_marginRight="5dp"
            android:lines="1"
            />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/ll_dots"
            android:orientation="horizontal"
            android:layout_marginRight="5dp"
            />
    </LinearLayout>
</merge>


还需要处理一些逻辑,我们希望在我们滑动的时候,可以无限的滑动:即当我们滑到最后一页的时候,我们依旧可以继续滑动。

看了网上许多都建议添加Integer.MAX_VALUE个item,然后不断的展示position % Integer.MAX_VALUE,这样就能实现无限右滑的效果了,这里我们只需要添加size * 2数目个item就能解决,思路如下:

假设我们有4个ImageView需要展示,分别为list0, list1, list2, list3,这时候我们重写PagerAdapter,将getCount的数量返回4 * 2个,即原来数目的两倍。分别为:list0, list1, list2, list3,list0, list1, list2, list3。当我们滑动的时候,当滑动实际位置为0的时候,我们设置当前页面为position=4,这样我们就可以前后滑动,而且向用户展示的图片是没有变化的。同理当我们滑动到最后一个位置7的时候,我们设置当前页面position=3。如下图

自定义控件之轮播图_第2张图片

在PagerAdapter方法中重写finishUpdate(ViewGroup container)方法,当完成滑动后进行位置的置换,因为我们切换位置的两张图片是相同的,所以在切换过程中,用户并不会感受到切换的改变,代码如下:

@Override
        public void finishUpdate(ViewGroup container) {
            int position = viewPager.getCurrentItem();
            if (position == 0) {
                // 当位置为0时,切换到跟显示相同的另一个位置
                position = urlList.size();
            } else if (position == adapter.getCount() - 1) {
                // 当位置为最后一个时,切换到跟显示相同的另一个位置
                position = urlList.size() - 1;
            }
            viewPager.setCurrentItem(position, false);
        }


这样一个完整的自定义轮播图就完成了,整体逻辑非常简单,而且也确实实现了我们想要的效果,没有多余拖沓的代码,效率完美

git下载地址:https://github.com/hjldev/BannerView

你可能感兴趣的:(自定义控件之轮播图)