直接上代码
使用的java和kotlin混合编写,如有需要自行转换
首先在attr.xml定义自定义属性,没有attr.xml的自建
<!-- 自定义滚动条目View-->
<declare-styleable name="LimitScroller">
<!--显示的条目数量-->
<attr name="limit" format="integer" />
<!--滚动速度,比如3000,滚动时间会持续3秒钟-->
<attr name="durationTime" format="integer" />
<!--滚动间隔,比如5000,滚动完成后停留5秒继续滚动-->
<attr name="periodTime" format="integer" />
</declare-styleable>
自定义滚动view
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
public class LimitScrollerView extends LinearLayout implements View.OnClickListener{
private String TAG = "LimitScrollerView";
private LinearLayout ll_content1, ll_content2;
private LinearLayout ll_now, ll_down; //当前可见的,下面不可见的(切换)
private int limit; //可见条目数量
private int durationTime; //动画执行时间
private int periodTime; //间隔时间
private int scrollHeight; //滚动高度(控件高度)
private int dataIndex;
private boolean isCancel; //是否停止滚动动画
private boolean boundData; //是否已经第一次绑定过数据
private final int MSG_SETDATA = 1;
private final int MSG_SCROL = 2;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == MSG_SETDATA){
boundData(true);
}else if(msg.what == MSG_SCROL){
if(isCancel)
return;
startAnimation();
}
}
};
public LimitScrollerView(Context context) {
this(context, null);
}
public LimitScrollerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LimitScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs){
LayoutInflater.from(context).inflate(R.layout.limit_scroller, this, true);
ll_content1 = findViewById(R.id.ll_content1);
ll_content2 = findViewById(R.id.ll_content2);
ll_now = ll_content1;
ll_down = ll_content2;
if(attrs!=null){
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LimitScroller);
limit = ta.getInt(R.styleable.LimitScroller_limit, 1);
durationTime = ta.getInt(R.styleable.LimitScroller_durationTime, 1000);
periodTime = ta.getInt(R.styleable.LimitScroller_periodTime, 1000);
ta.recycle(); //注意回收
Log.v(TAG, "limit="+limit);
Log.v(TAG, "durationTime="+durationTime);
Log.v(TAG, "periodTime="+periodTime);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//设置高度为整体高度的两倍,以达到遮盖预备容器的效果并不压缩空间
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()*2);
//此处记下控件的高度,此高度就是动画执行时向上滚动的高度
scrollHeight = getHeight()/2;
ll_now.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,scrollHeight));
ll_down.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,scrollHeight));
Log.w(TAG, "getMeasuredHeight="+getMeasuredHeight());
Log.w(TAG, "scrollHeight="+scrollHeight);
}
private void startAnimation(){
if(isCancel)
return;
//当前展示的容器,从当前位置(0),向上滚动scrollHeight
ObjectAnimator anim1 = ObjectAnimator.ofFloat(ll_now, "Y",0, -scrollHeight);
//预备容器,从当前位置,向上滚动scrollHeight
ObjectAnimator anim2 = ObjectAnimator.ofFloat(ll_down, "Y",scrollHeight, ll_down.getY()-scrollHeight);
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(durationTime);
animSet.playTogether(anim1, anim2);
animSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//滚动结束后,now的位置变成了-scrollHeight,这时将他移动到最底下
ll_now.setY(scrollHeight);
//down的位置变为0,也就是当前看见的
ll_down.setY(0);
LinearLayout temp = ll_now;
ll_now = ll_down;
ll_down = temp;
//给不可见的控件绑定新数据
boundData(false);
handler.removeMessages(MSG_SCROL);
if(isCancel) {
return;
}
handler.sendEmptyMessageDelayed(MSG_SCROL, periodTime);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animSet.start();
}
/**
* 向容器中添加子条目
* @param first
*/
private void boundData(boolean first){
if(adapter==null || adapter.getCount()<=0)
return;
if(first){
//第一次绑定数据,需要为两个容器添加子条目
boundData = true;
ll_now.removeAllViews();
for(int i = 0; i<limit; i++){
if(dataIndex>=adapter.getCount())
dataIndex = 0;
View view = adapter.getView(dataIndex);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
//设置点击监听
view.setClickable(true);
view.setOnClickListener(this);
ll_now.addView(view);
dataIndex ++;
}
}
//每次动画结束之后,为预备容器添加新条目
ll_down.removeAllViews();
for(int i = 0; i<limit; i++){
if(dataIndex>=adapter.getCount())
dataIndex = 0;
View view = adapter.getView(dataIndex);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
//设置点击监听
view.setClickable(true);
view.setOnClickListener(this);
ll_down.addView(view);
dataIndex ++;
}
}
@Override
public void onClick(View v) {
if(clickListener!=null){
Object obj = v.getTag();
clickListener.onItemClick(obj);
}
}
public interface LimitScrollAdapter{
public int getCount();
public View getView(int index);
}
private LimitScrollAdapter adapter;
public interface OnItemClickListener{
public void onItemClick(Object obj);
}
private OnItemClickListener clickListener;
/**********************public API 以下为暴露的接口***********************/
/**
* 1、设置数据适配器
* @param adapter
*/
public void setDataAdapter(LimitScrollAdapter adapter){
this.adapter = adapter;
handler.sendEmptyMessage(MSG_SETDATA);
}
/**
* 2、开始滚动
* 应该在两处调用此方法:
* ①、Activity.onStart()
* ②、MyLimitScrllAdapter.setDatas()
*/
public void startScroll(){
if(adapter==null||adapter.getCount()<=0)
return;
if(!boundData){
handler.sendEmptyMessage(MSG_SETDATA);
}
isCancel = false;
Log.e(TAG, "开始滚动");
handler.removeMessages(MSG_SCROL); //先清空所有滚动消息,避免滚动错乱
handler.sendEmptyMessageDelayed(MSG_SCROL, periodTime);
}
/**
* 3、停止滚动
* 当在Activity不可见时,在Activity.onStop()中调用
*/
public void cancel(){
isCancel = true;
Log.e(TAG, "停止滚动");
}
/**
* 4、设置条目点击事件
* @param listener
*/
public void setOnItemClickListener(OnItemClickListener listener){
this.clickListener = listener;
}
}
新建adapter类继承LimitScrollAdapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import java.util.List;
public class MyLimitScrollAdapter implements LimitScrollerView.LimitScrollAdapter {
private Context context;
private LimitScrollerView limitScroll;
private List<MovieBean> datas;//数据实体类需自定义
public MyLimitScrollAdapter(Context context, LimitScrollerView limitScrollerView) {
this.limitScroll = limitScrollerView;
this.context = context;
}
public void setDatas(List<MovieBean> datas) {
this.datas = datas;
//API:2、开始滚动
limitScroll.startScroll();
}
@Override
public int getCount() {
return datas == null ? 0 : datas.size();
}
@Override
public View getView(int index) {
//在这里做自己的视图自定义,数据与视图的绑定自行完成,下面仅作参考
View itemView = LayoutInflater.from(context).inflate(R.layout.limit_scroller_item, null, false);
TextView tv_text = (TextView) itemView.findViewById(R.id.tv_text);
//绑定数据
String data = datas.get(index).getName();
itemView.setTag(data);
tv_text.setText(data);
return itemView;
}
}
布局文件中
<***.LimitScrollerView
android:id="@+id/limitScroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dp_6"
android:layout_marginEnd="@dimen/dp_6"
app:durationTime="1000"
app:limit="1"
app:periodTime="2000" />
activity或fragment中
private fun initLimitScroll() {
//可滚动的条目初始化
var myLimitScrollAdapter = MyLimitScrollAdapter(context, binding?.limitScroll)
binding?.limitScroll?.setOnItemClickListener {
//可滚动的条目点击事件
}
binding?.limitScroll?.setDataAdapter(myLimitScrollAdapter)
myLimitScrollAdapter.setDatas(data.movies)
}
override fun onStart() {
super.onStart()
binding?.limitScroll?.startScroll()
}
override fun onStop() {
super.onStop()
binding?.limitScroll?.cancel()
}