昨天写了关于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);
}
}
添加图片滚动的逻辑,在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。如下图
在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