搭建直播app源代码,实现一个循环滚动功能

序言

在直播app源代码业务中需要显示一个循环滚动的控件,内容可以循环滚动,可以自动滚动,手指触摸的时候会暂停。 由于目前的方案都是基于ViewPager或者RecycleView的。还需要实现Adapter,需要拦截各种事件。使用成本比较高。于是我就自定义了一个控件实现该功能,

使用

使用起来很简单。把需要显示的控件放置在其中就行。就和普通的HorizontalScrollView用法一样。 不过子控件必须要LoopLinearLayout

在这里插入图片描述

效果

  • 1.支持左右循环滚动
  • 2.支持自动滚动
  • 3.支持点击事件
  • 4.触摸暂停
  • 5.支持惯性滚动
  • 6.一共不到300行代码,逻辑简单易于扩展

原理

通过继承自HorizontalScrollView实现,重新onOverScrolledscrollTo 方法在调用supper方法之前,对是否到达边界进行判断,如果到达就调用LoopLinearLayout.changeItemsToRight() 方法对内容重新摆放。

摆放使用的是 child.layout() 的方法,没有性能问题。摆放完成以后,对scrollX进行重新赋值。

需要注意的是在HorizontalScrollView中有一个负责惯性滚动的OverScroller

在这里插入图片描述

但是在直播app源代码调用其fling方法之前会设置maxX这导致无法滚动到控件内容之外。所以使用反射修改了这个类。拦截了fling方法

在这里插入图片描述

而动画的时长设置的是滚动一个LoopScrollView宽度需要的时间。还有就是无限循环的动画需要在 onDetachedFromWindow中移除,避免直播app源代码内存泄漏

源码

LoopLinearLayout

package com.example.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import androidx.annotation.Nullable;

import java.util.List;

/**
 * Created by zhuguohui
 * Date: 2021/11/30
 * Time: 10:46
 * Desc:
 */
public class LoopLinearLayout extends LinearLayout {
    public LoopLinearLayout(Context context) {
        this(context, null);
    }

    public LoopLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    public void changeItemsToRight(List<View> moveItems, int offset) {

        int offset2 = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View childAt = getChildAt(i);
            if (!moveItems.contains(childAt)) {
                MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
                offset2 += childAt.getWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                childAt.layout(childAt.getLeft() - offset, childAt.getTop(), childAt.getRight() - offset, childAt.getBottom());
            }
        }
        for(View view:moveItems){
            view.layout(view.getLeft()+offset2,view.getTop(),view.getRight()+offset2,view.getBottom());
        }
    }
    public void changeItemsToLeft(List<View> moveItems, int offset) {

        int offset2 = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View childAt = getChildAt(i);
            if (!moveItems.contains(childAt)) {
                MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
                offset2 += childAt.getWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                childAt.layout(childAt.getLeft() + offset, childAt.getTop(), childAt.getRight() + offset, childAt.getBottom());
            }
        }
        for(View view:moveItems){
            view.layout(view.getLeft()-offset2,view.getTop(),view.getRight()-offset2,view.getBottom());
        }
    }


}

LoopScrollView

package com.example.myapplication;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.HorizontalScrollView;
import android.widget.OverScroller;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class LoopScrollView extends HorizontalScrollView {

    private LoopScroller loopScroller;
    private ValueAnimator animator;

    public LoopScrollView(Context context) {
        this(context, null);
    }

    public LoopScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOverScrollMode(OVER_SCROLL_ALWAYS);
        try {
            @SuppressLint("DiscouragedPrivateApi")
            Field field =HorizontalScrollView.class.getDeclaredField("mScroller");
            field.setAccessible(true);
            loopScroller = new LoopScroller(getContext());
            field.set(this, loopScroller);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(changed||animator==null){
            buildAnimation();
        }
    }

    private void buildAnimation() {
        if(animator!=null){
            animator.cancel();
            animator=null;
        }
        animator = ValueAnimator.ofInt(getWidth() - getPaddingRight() - getPaddingLeft());
        animator.setDuration(5*1000);
        animator.setRepeatCount(-1);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            int lastValue;
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value= (int) animation.getAnimatedValue();
                int scrollByX=value-lastValue;
          //      Log.i("zzz","scroll by x="+scrollByX);
                scrollByX=Math.max(0,scrollByX);
                if(userUp) {
                    scrollBy(scrollByX, 0);
                }
                lastValue=value;
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
            }

        });
        animator.start();
    }

    static   class LoopScroller extends OverScroller{
        public LoopScroller(Context context) {
            super(context);
        }

      @Override
      public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY) {
          super.fling(startX, startY, velocityX, velocityY, Integer.MIN_VALUE,Integer.MAX_VALUE, minY, maxY, 0, overY);
      }
  }




    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if(animator!=null){
            animator.cancel();
            animator.removeAllListeners();
            animator = null;
        }
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        if (userUp) {
            //scroller再滚动
            scrollX=loopScroller.getCurrX();
            int detailX = scrollX - lastScrollX;
            lastScrollX = scrollX;
            if(detailX==0){
                return;
            }
            scrollX = detailX + getScrollX();

        }
        int moveTo = moveItem(scrollX,clampedX);

        super.onOverScrolled(moveTo, scrollY, false, clampedY);
    }

    boolean userUp = true;
    int lastScrollX = 0;

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            userUp = true;
            lastScrollX = getScrollX();
        } else {
            userUp = false;
        }
        return super.onTouchEvent(ev);
    }
    @Override
    public void scrollTo(int x, int y) {
        int scrollTo = moveItem(x, false);
        super.scrollTo(scrollTo, y);
    }


    private int moveItem(int scrollX, boolean clampedX) {

        int toScrollX = scrollX;

        if (getChildCount() > 0) {
            if (!canScroll(scrollX,clampedX)) {
                boolean toLeft=scrollX<=0;
                int mWidth=getWidth()-getPaddingLeft()-getPaddingRight();
                //无法向右滚动了,将屏幕外的item,移动到后面
                List<View> needRemoveViewList = new ArrayList<>();
                LoopLinearLayout group = (LoopLinearLayout) getChildAt(0);
                int removeItemsWidth = 0;
                boolean needRemove = false;
                for (int i = group.getChildCount() - 1; i >= 0; i--) {
                    View itemView = group.getChildAt(i);
                    MarginLayoutParams params = (MarginLayoutParams) itemView.getLayoutParams();
                    if(toLeft){
                        int itemLeft = itemView.getLeft() - params.leftMargin;
                        if (itemLeft >= mWidth) {
                            //表示之后的控件都需要移除
                            needRemove = true;
                        }
                    }else{
                        int itemRight = itemView.getRight() + params.rightMargin;
                        if (itemRight <= scrollX) {
                            //表示之后的控件都需要移除
                            needRemove = true;
                        }
                    }

                    if (needRemove) {
                        int itemWidth = itemView.getWidth() + params.rightMargin + params.leftMargin;
                        removeItemsWidth += itemWidth;
                        needRemoveViewList.add(0,itemView);
                    }
                    needRemove=false;
                }
                if(!toLeft){
                    group.changeItemsToRight(needRemoveViewList,removeItemsWidth);
                    toScrollX -=removeItemsWidth;
                }else{
                    group.changeItemsToLeft(needRemoveViewList,removeItemsWidth);
                    toScrollX +=removeItemsWidth;
                }

            }

        }
        return Math.max(0, toScrollX);
    }

    private boolean canScroll(int scrollX, boolean clampedX) {
        if(scrollX<0){
            return false;
        }
        if(scrollX==0&&clampedX){
            //表示向左划不动了
            return  false;
        }
        View child = getChildAt(0);
        if (child != null) {
            int childWidth = child.getWidth();
            return getWidth() + scrollX < childWidth + getPaddingLeft() + getPaddingRight();
        }
        return false;
    }
}

最后所有的功能只依赖上述两个类,关于动画的时长写死在直播app源代码的类中的,没有抽成方法。有需要的自己去改吧。
声明:本文由云豹科技转发自solo_99博客,如有侵权请联系作者删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值