Android自定义View——可自动上下滚动的条目

直接上代码

使用的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()
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值