前言: 公司产品须要新增悬浮广告条的功能,要求是能够循环滚动,而且点击相应的浮条会跳转到相应的界面,在实现这个功能的时候遇到一些坑,幸运的是最后从这些坑中爬了出来。这篇文章的主要内容就是介绍功能的实现以及爬坑的经验。html
在文章开始前,先看下最后实现的效果,最终的效果以下图java
咱们已经知道了产品的需求,下面要作的就是分析这个需求应该怎样实现,首先咱们要实现的功能就是让广告条循环滚动,看最终的效果图能够发现,滚动的方向是由下往上滚动,平时咱们见的banner图都是左右滚动的,若是是左右滚动的就好办了,能够经过ViewPager
来实现。可是这个上下滚动的应该怎么实现呢?首先,想到的是利用ViewFlipper
这个系统控件,可是这个控件只能知足循环滚动这个功能,咱们还有一个需求是点击不一样的浮条跳转不一样的内容呢!这个功能ViewFlipper
就没法知足了。既要循环滚动又要每一个浮条有相应的点击事件,天然的就想到了RecyclerView
,下面就利用RecyclerView来实现这些需求。android
RecyclerView的使用相信你们都会的,可是这里有个问题是怎样让RecyclerView循环滚动?再把问题细分一下,首先就是怎样让RecyclerView本身滚动,而后是怎样实现里面的内容循环。git
怎样让RecyclerView本身滚动呢?经过查官方的Api,发现RecyclerView的LayoutManager中有这样一个方法github
这个方法的说明是异步
使用提供的SmoothScroller开始平滑滚动。ide
好了,如今咱们知道了这个方法的做用是让RecyclerView平滑滚动的,既然是让RecyclerView平滑滚动,那么咱们确定要告诉startSmoothScroll
方法,RecyclerView怎样滚动,如,滚动的方向、距离、速度等。上面的方法说明也说了根据提供的SmoothScroller
滚动,所以这里咱们要实现SmoothScroller
类来制定一些滚动的规则,查看源码能够发现SmoothScroller
是抽象类,而官方文档中说它的已知的直接实现类是LinearSmoothScroller,因此这里直接实例化LinearSmoothScroller
,重写相应的方法便可。具体代码以下ui
mSmoothScroller = new LinearSmoothScroller(this) {
@Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 3f / (displayMetrics.density);
}
};
复制代码
能够看到这里重写了两个方法。getVerticalSnapPreference
这个方法是制定对齐的规则,就是RecyclerView里面的item顶部或底部与RecyclerView的对齐方式,这里有三种对齐方式,如下是官方文档中对这三种对齐方式的具体说明this
再看下calculateSpeedPerPixel
这个方法,这个方法是用来计算滚动的速度的,返回值是滚动一个像素花费的毫秒数。displayMetrics.density
这个是1dp对应的像素密度,就是1dp等于多少像素。google
注:SmoothScroller
是将目标item滚动到RecyclerView中,即让目标item在RecyclerView中可见。
已经设置好滚动的规则了,下面要作的就是让RecyclerView中的item滚动,而且循环滚动。
在实现循环滚动以前,看下实现滚动的代码,以下
private void startAuto() {
if (mAutoTask != null && !mAutoTask.isDisposed()) {
mAutoTask.dispose();
}
mAutoTask = Observable.interval(1, 2, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) {
mSmoothScroller.setTargetPosition(aLong.intValue());
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager!=null)
layoutManager.startSmoothScroll(mSmoothScroller);
}
});
}
复制代码
从这段代码中能够看到,用RxJava中的interval
方法实现了一个循环数字递增定时器,时间间隔是2s。mSmoothScroller.setTargetPosition(aLong.intValue());
这句代码就是设置哪一个item出如今RecyclerView中。layoutManager.startSmoothScroll(mSmoothScroller);
这句代码实际上调用的就是LinearSmoothScroller
类中的start
方法,这段代码实现的功能就是每隔两秒,就让设置的目标item平滑滚动到RecyclerView中。
如今已经开始滚动了,那么怎么让目标item重复出现呢?其实这很简单,就是将itemCount设置成无限大,具体代码以下
@Override
public int getItemCount() {
return Integer.MAX_VALUE;
}
@Override
public void onBindViewHolder(@NonNull final AdViewHolder holder, int position) {
if (mDynamicAdsDetails.size() != 0) {
String media1 = mDynamicAdsDetails.get(position % mDynamicAdsDetails.size());
Picasso.get()
.load(media1)
.error(R.mipmap.ic_launcher)
.placeholder(R.mipmap.ic_launcher)
.into( holder.ivFlipperItem);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//控制点击频率
if ((System.currentTimeMillis() - between) / 1000 < 1) {
return;
}
between = System.currentTimeMillis();
Toast.makeText(mContext,"点击了第"+holder.getAdapterPosition() % mDynamicAdsDetails.size()+"个",Toast.LENGTH_SHORT).show();
}
});
}
复制代码
注:将itemCount设置成无限大后,取列表中的值时,不能直接根据相应的position
来取值了,应该这样取mDynamicAdsDetails.get(position % mDynamicAdsDetails.size())
。
这样就能实现循环滚动了。
这样实现看起来显然没问题,可是项目跑起来后,却发现图片居然不是循环显示的,而是偶尔会一张图片出现屡次,而后才是下一张图片。分析了一下缘由,认为是RecyclerView的复用问题,图片异步请求的结果尚未返回回来,复用了上次的控件,因此就出现一个图片显示屡次的问题了。解决方法就是给ImageView设置Tag,具体代码以下
if (mDynamicAdsDetails.size() != 0) {
String media1 = mDynamicAdsDetails.get(position % mDynamicAdsDetails.size());
holder.ivFlipperItem.setTag(media1);
Picasso.get()
.load(media1)
.error(R.mipmap.ic_launcher)
.placeholder(R.mipmap.ic_launcher)
.into(new com.squareup.picasso.Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (mDynamicAdsDetails.get(holder.getAdapterPosition() % mDynamicAdsDetails.size()).equals(holder.ivFlipperItem.getTag())) {
holder.ivFlipperItem.setScaleType(ImageView.ScaleType.FIT_XY);
holder.ivFlipperItem.setImageBitmap(bitmap);
}
}
@Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
}
复制代码
先看下滑动RecyclerView时出现的问题,如图
能够发现,虽然能够滑动RecyclerView,可是滑动事后,item会回退回来,而后继续上次的位置开始滚动。这个问题解决方法有两种
interval
方法中把滑动的位置设置为目标位置。由于需求没有能够滑动的这个功能,因此这里采用方法2,禁止RecyclerView的滑动,详细代码以下
public class AutoScrollRecyclerView extends RecyclerView {
private int mState;
private OnScrollListener mScrollListener;
public AutoScrollRecyclerView(Context context) {
this(context,null);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
return false;
case MotionEvent.ACTION_POINTER_UP:
return false;
}
return true;
}
}
复制代码
这里重写了RecyclerVieiew的onTouchEvent
方法,当滑动式返回false
,不消费滑动的动做。
可是,这么作以后会有新的问题,就是当图片在滚动时,咱们点击图片,图片会暂停住,这里采用的解决方法是监听RecyclerView 的滚动状态,只有当RecyclerView滑动中止时,才不拦截事件,不然就拦截事件。具体代码以下
public class AutoScrollRecyclerView extends RecyclerView {
private int mState;
private OnScrollListener mScrollListener;
public AutoScrollRecyclerView(Context context) {
this(context,null);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
mState = newState;
}
};
//添加RecyclerView的滑动监听
addOnScrollListener(mScrollListener);
}
//判断是否拦截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
return mState != 0;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
return mState == 0;
case MotionEvent.ACTION_MOVE:
return false;
case MotionEvent.ACTION_POINTER_UP:
return false;
}
return true;
}
}
复制代码
好了,这样就解决了滑动RecyclerView出现的问题。
广告的样式是能够本身定义的,不只仅是图片,还能够实现图文混排等,只须要修改layout文件便可,这里为了方便就直接在layout中放了一张图片。
实现的功能挺简单的,可是若是对RecyclerView滑动的方法不熟悉的话,实现起来仍是有点难度的,还有就是咱们在编写代码的时候不只要实现功能,还有注意对一些细节的处理,若是细节处理的很差,是很影响用户体验的。一些细节方面的问题是很考验技能的,固然对技能提高的帮助也是很大的。
最后,固然是放出源码了,点击这里获取源码
转载请注明出处:www.wizardev.cn