之前我也写过一篇关于Android竖直跑马灯效果的控件,不过这个控件是基于子Item是纯文本的情况,详情请移步:Android 自定义View实现竖直跑马灯效果,不过后面项目需求发生了变化,必须要整个Item包括图片啊文本啥的一起上下滚动,这个控件顿时就傻眼了,旧的设计架构是不行了,但是旧的思路依然可行。本文采取得思路和之前的是一样的,只是实现方式不同。放上效果图,DEMO在最下面
首先这里将大概的一些重点讲解一下:
- Item的布局采用XML的方式填充,这样更满足android的MVC开发模式
- Item的布局的数据填充,采用泛型+建造者模式实现
- 无线滚动的思路和上一篇文章一样,即在内存中只存在两个View,一个处于可见处,一个处于下方的不可见处。
- 重写ViewGroup的onMeasure和onLayout方法,在onMeasure方法中设置控件的高度和Item的高度一致,在onLayout方法进行初始化布局。
- 开启属性动画,动态得对子View的布局进行重设定,并且刷新UI。在动画完毕的监听中,对当前索引进行判断更新迭代
现在开始讲解,首先是一堆属性的声明和链式设置方法:
//滚动间隔时间 和滚动动画时间
public static final int DURATION_SCROLL = 3000;
public static final int DURATION_ANIMATOR = 1000;
//实体集合和子控件集合
private List<T> beans = new ArrayList<T>();
private List<View> views = new ArrayList<View>(2);
private int itemLayoutId;
private Handler handler = new Handler();
//宽度和高度(包括padding)
private int width;
private int height;
//第一个子View的中点Y坐标
private int centerY;
//是否结束滚动
private boolean isStopScroll = true;
//当前的索引
private int current;
private OnItemClickListener listener;
private OnItemBuilder builder;
public VerticalMarqueeLayout(Context context) {
super(context);
}
public VerticalMarqueeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticalMarqueeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public int getCurrentIndex(){
return current;
}
public VerticalMarqueeLayout listener(OnItemClickListener listener){
this.listener = listener;
return this;
}
public VerticalMarqueeLayout builder(OnItemBuilder builder){
this.builder = builder;
return this;
}
/**
* 设置实体集合和item布局id
*/
public VerticalMarqueeLayout datas(List<T> beans, int itemLayoutId){
this.beans.clear();
this.beans.addAll(beans);
this.itemLayoutId = itemLayoutId;
return this;
}
变量属性的申明已经有了足够的注释,这里就不累述了。主要是下面三个方法设置的方法,其中listener就是设置本类的一个监听器,datas方法就是将数据和所需要的Item布局id设置进来,这里的数据采用了泛型的模式。而这三个方法其中最重要的是builder方法,设置一个建造者,这个建造者是干嘛用的呢?我们接下来继续看:
public interface OnItemClickListener{
void onItemClick(int position);
}
public abstract class OnItemBuilder{
public abstract void assemble(View view, T t);
private void measure(View view){
view.measure(MeasureSpec.makeMeasureSpec(width - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
public void builder(View view, T t){
//先装配数据
assemble(view, t);
//重新测量
measure(view);
}
}
点击监听器就不说了,主要是这个onItemBuilder基类,首先这个类会有一个装配方法assemble方法暴露出来给外界设置,然后又一个measure方法进行View的测量,其中的width变量是指本控件的宽度,后面会介绍。最后在builder方法中,将这两个方法进行组装。代码很少但是这的确就是建造者模式。好了这个类我们知道实现方式了,但是怎么来使用这个类呢,别急,慢慢来。
public void commit(){
if(builder == null){
throw new IllegalStateException("must invoke the method [builder(OnItemBuilder)]");
}
this.views.clear();
if(beans != null && beans.size() != 0){
View view = View.inflate(getContext(), itemLayoutId, null);
//在这里填充布局参数
if(builder != null){
builder.builder(view, beans.get(0));
}
this.views.add(view);
//这里通过手动设置全屏宽度的方式add
addViewWidthMatchParent(view);
//如果大于等于2个,初始化第二个View
if(beans.size() > 1){
View view1 = View.inflate(getContext(), itemLayoutId, null);
if(builder != null){
builder.builder(view1, beans.get(1));
}
this.views.add(view1);
addViewWidthMatchParent(view1);
}
//手动触发onMeasure和onDraw
LayoutParams params = getLayoutParams();
if(params != null){
setLayoutParams(params);
invalidate();
}
current = 0;
setOnClickList