Android 自定义ViewGroup实现整个Item布局竖直跑马灯效果

本文介绍如何在Android中实现整个包含图片和文本的Item竖直跑马灯效果,采用XML布局填充,泛型+建造者模式填充数据,并通过重写ViewGroup的onMeasure和onLayout方法,结合属性动画实现无限滚动。详细步骤和DEMO提供。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前我也写过一篇关于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
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值