昨天写了关于RecyclerView添加头部布局的方法,一般我们的头部布局都会添加什么呢?自然是一个轮播形式的广告位。关于RecyclerView我们使用了比较简单的刷新提示,轮播图我们也要做一个简单而且实用,并且效率并不低的自定义控件!
关于自定义控件,一般实现的方式有两种。第一种是继承自View,整个界面用画笔在画布上一点点的画出来。今天的轮播图我们使用第二种方法:组合控件,把几个布局组合在一起形成一个界面,然后处理一下逻辑,就形成了“独特”的控件。
所谓组合控件,就是在一个特定的布局上面,把几个控件组合起来,这其实就跟我们平常写个xml布局是一样,只不过我们多加了一些处理的逻辑,让他可以自由的表现出需求的效果,例如轮播
我们首先要设计这个控件,大体如下:
首先,创建一个类,继承自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); } }
/** * 循环滚动的逻辑 */ 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。如下图
在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