自定义view 可自动换行滑动的LinearLayout

自定义view 代码

package com.example.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import java.util.ArrayList;
import java.util.List;

/**
 *   on 2021/6/15 09:33
 * 作用:自动换行的LinearLayout
 */

public class WarpLinearLayout extends ViewGroup {

    private Type mType;
    private List<WarpLine> mWarpLineGroup;
    public WarpLinearLayout(Context context) {
        this(context, null);
    }
    public WarpLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, R.style.WarpLinearLayoutDefault);
    }
    public WarpLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mType = new Type(context, attrs);
        init(context);
    }
//    MeasureSpec.UNSPECIFIED:未指定模式,在这个模式下父控件不会干涉子 View 想要多大的尺寸。
//    MeasureSpec.EXACTLY:精确模式,视图应该是这么多像素,无论它实际上有多大。//match_parent 或者xxdp
//    MeasureSpec.AT_MOST:最多模式:视图可以是它需要的任何大小,以显示它需要显示的内容。wrap_content
//    asureSpec.EXACTLY:表示精确的,比如我告诉你宽20,那就是20,和父容器一样宽,大小很明确
//    MeasureSpec.AT_MOST:表示最大值,最大不能超过多少
//    MeasureSpec.UNSPECIFIED:无限制
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int withMode = MeasureSpec.getMode(widthMeasureSpec);
        int withSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int with = 0;

        int childCount = getChildCount();
        /**
         * 在调用childView。getMeasre之前必须先调用该行代码,用于对子View大小的测量
         */
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        /**
         * 计算宽度
         */
        switch (withMode) {
            case MeasureSpec.EXACTLY:
                Log.d("自定义view","精准宽度");//match_parent 或者xxdp
                with = withSize;
                break;
            case MeasureSpec.AT_MOST:
                Log.d("自定义view","宽度AT_MOST"); //wrap_content
                for (int i = 0; i < childCount; i++) {
                    if (i != 0) {
                        with += mType.horizontal_Space;
                    }
                    with += getChildAt(i).getMeasuredWidth();
                }
                with += getPaddingLeft() + getPaddingRight();
                with = with > withSize ? withSize : with;
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.d("自定义view","宽度UNSPECIFIED");
                for (int i = 0; i < childCount; i++) {
                    if (i != 0) {
                        with += mType.horizontal_Space;
                    }
                    with += getChildAt(i).getMeasuredWidth();
                }
                with += getPaddingLeft() + getPaddingRight();
                break;
            default:
                with = withSize;
                break;

        }

        /**
         * 根据计算出的宽度,计算出所需要的行数
         */
        WarpLine warpLine = new WarpLine();
        /**
         * 不能够在定义属性时初始化,因为onMeasure方法会多次调用
         */
        mWarpLineGroup = new ArrayList<WarpLine>();
        for (int i = 0; i < childCount; i++) {
            if (warpLine.lineWidth + getChildAt(i).getMeasuredWidth() + mType.horizontal_Space > with) {
                if (warpLine.lineView.size() == 0) {
                    warpLine.addView(getChildAt(i));
                    mWarpLineGroup.add(warpLine);
                    warpLine = new WarpLine();
                } else {
                    mWarpLineGroup.add(warpLine);
                    warpLine = new WarpLine();
                    warpLine.addView(getChildAt(i));
                }
            } else {
                warpLine.addView(getChildAt(i));
            }
        }
        /**
         * 添加最后一行
         */
        if (warpLine.lineView.size() > 0 && !mWarpLineGroup.contains(warpLine)) {
            mWarpLineGroup.add(warpLine);
        }
        int height = measureChildCountHeight();
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = height > heightSize ? heightSize : height;
                break;
            case MeasureSpec.UNSPECIFIED:
                break;
            default:
                break;
        }
        setMeasuredDimension(with, height);
    }

    protected int measureChildCountHeight( ) {
        int height = 0;
        /**
         * 计算宽度
         */
        height = getPaddingTop() + getPaddingBottom();
        if(mWarpLineGroup!=null){
            for (int i = 0; i < mWarpLineGroup.size(); i++) {
                if (i != 0) {
                    height += mType.vertical_Space;
                }
                height += mWarpLineGroup.get(i).height;
            }
        }

        return height;
    }

    private Scroller mScroller;//这个scroller是为了平滑滑动
    //实现平滑地回滚
    /**
     * 最叼的还是这个方法,平滑地回滚,从当前位置滚到目标位置
     * @param dx
     * @param dy
     */
    void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 1000);//从当前滑动的位置,平滑地过度到目标位置
        invalidate();
    }
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    //记录Y轴当前手机滑动位置。并计算滑动的距离
    float lastY=0;//最近一次Y 坐标
    float donwy=0;//按下时Y 坐标
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);//在onTouchEvent这里,截取event对象
        ViewConfiguration configuration = ViewConfiguration.get(getContext());
        if (event.getAction()== MotionEvent.ACTION_MOVE){
          //计算上次跟本次滑动长度
          float diff=   lastY-event.getY();
            //当前滑动位置+当前的滑动差就是本次的应该滑动位置
           int nowY = (int) diff*3+ getScrollY();

            //计算子view 实际高度=子views实际高度-当前view 实际高度就是当前可以滑动到最大值是多少。到达了这个值就是底部了。
            int height = measureChildCountHeight();//子view 实际高度
            int maxY= height- getMeasuredHeight();//最大支持滑动Y 的位置

           //如果滑动位置小于0 则到了顶部。越过了顶部。需要拦截到顶部即可。否则越界
            if(nowY<0){
                nowY=0;
            }
            //当前滑动如果超过最大Y 则会发生底部越界。所以这里最大到底部Y即可
            if(nowY>maxY ){
                nowY=maxY;
            }
            scrollTo(0,nowY);
            lastY= event.getY();
        } else if (event.getAction()== MotionEvent.ACTION_DOWN){
            donwy= lastY= event.getY();
            return  true;
        }   else if (event.getAction()== MotionEvent.ACTION_UP){
            mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity());//计算,最近的event到up之间的速率
            float yVelocity = mVelocityTracker.getYVelocity();//当前横向的移动速率
            mVelocityTracker.clear();
//            因为abs会出现负数所以abs 下
            int scal= (int) (Math.abs(yVelocity)/500);
            if(scal<=0){
                scal=1;
            }
            //计算上次跟本次滑动长度
            float diff=   donwy-event.getY();
            diff*=scal;
            //当前滑动位置+当前的滑动差就是本次的应该滑动位置
            int nowY = (int) diff*3+ getScrollY();
            //计算子view 实际高度=子views实际高度-当前view 实际高度就是当前可以滑动到最大值是多少。到达了这个值就是底部了。
            int height = measureChildCountHeight();//子view 实际高度
            int maxY= height- getMeasuredHeight();//最大支持滑动Y 的位置
            //如果滑动位置小于0 则到了顶部。越过了顶部。需要拦截到顶部即可。否则越界
            if(nowY<0){
                diff=0-getScrollY();
            }
            if(nowY>maxY ){
                diff=maxY-getScrollY();
            }
            smoothScrollBy(0, (int) diff);
            Log.d("自定义View",yVelocity+"");
            return  true;
        }

        return super.onTouchEvent(event);
    }
    //第一步,定义一个追踪器引用
    private VelocityTracker mVelocityTracker;//滑动速度追踪器

    private void init(Context context) {
        mScroller = new Scroller(context);
        //初始化追踪器
        mVelocityTracker = VelocityTracker.obtain();//获得追踪器对象,这里用obtain,按照谷歌的尿性,应该是考虑了对象重用
    }

    //子view 摆放
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        t = getPaddingTop();
        for (int i = 0; i < mWarpLineGroup.size(); i++) {
            int left = getPaddingLeft();
            WarpLine warpLine = mWarpLineGroup.get(i);
            int lastWidth = getMeasuredWidth() - warpLine.lineWidth;
            for (int j = 0; j < warpLine.lineView.size(); j++) {
                View view = warpLine.lineView.get(j);
                if (isFull()) {//需要充满当前行时
                    view.layout(left, t, left + view.getMeasuredWidth() + lastWidth / warpLine.lineView.size(), t + view.getMeasuredHeight());
                    left += view.getMeasuredWidth() + mType.horizontal_Space + lastWidth / warpLine.lineView.size();
                } else {
                    switch (getGrivate()) {
                        case 0://右对齐
                            view.layout(left + lastWidth, t, left + lastWidth + view.getMeasuredWidth(), t + view.getMeasuredHeight());
                            break;
                        case 2://居中对齐
                            view.layout(left + lastWidth / 2, t, left + lastWidth / 2 + view.getMeasuredWidth(), t + view.getMeasuredHeight());
                            break;
                        default://左对齐
                            view.layout(left, t, left + view.getMeasuredWidth(), t + view.getMeasuredHeight());
                            break;
                    }
                    left += view.getMeasuredWidth() + mType.horizontal_Space;
                }
            }
            t += warpLine.height + mType.vertical_Space;
        }
        Log.d("自定义view","onLayout"+getScrollY());
    }

    /**
     * 用于存放一行子View
     */
    private final class WarpLine {
        private List<View> lineView = new ArrayList<View>();
        /**
         * 当前行中所需要占用的宽度
         */
        private int lineWidth = getPaddingLeft() + getPaddingRight();
        /**
         * 该行View中所需要占用的最大高度
         */
        private int height = 0;

        private void addView(View view) {
            if (lineView.size() != 0) {
                lineWidth += mType.horizontal_Space;
            }
            height = height > view.getMeasuredHeight() ? height : view.getMeasuredHeight();
            lineWidth += view.getMeasuredWidth();
            lineView.add(view);
        }
    }

    /**
     * 对样式的初始化
     */
    private final static class Type {
        /*
         *对齐方式 right 0,left 1,center 2
         */
        private int grivate;
        /**
         * 水平间距,单位px
         */
        private float horizontal_Space;
        /**
         * 垂直间距,单位px
         */
        private float vertical_Space;
        /**
         * 是否自动填满
         */
        private boolean isFull;

        Type(Context context, AttributeSet attrs) {
            if (attrs == null) {
                return;
            }
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WarpLinearLayout);
            grivate = typedArray.getInt(R.styleable.WarpLinearLayout_grivate, grivate);
            horizontal_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_horizontal_Space, horizontal_Space);
            vertical_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_vertical_Space, vertical_Space);
            isFull = typedArray.getBoolean(R.styleable.WarpLinearLayout_isFull, isFull);
        }
    }

    public int getGrivate() {
        return mType.grivate;
    }

    public float getHorizontalSpace() {
        return mType.horizontal_Space;
    }

    public float getVerticalSpace() {
        return mType.vertical_Space;
    }

    public boolean isFull() {
        return mType.isFull;
    }

    public void setGrivate(int grivate) {
        mType.grivate = grivate;
    }

    public void setHorizontalSpace(float horizontal_Space) {
        mType.horizontal_Space = horizontal_Space;
    }

    public void setVerticalSpace(float vertical_Space) {
        mType.vertical_Space = vertical_Space;
    }

    public void setIsFull(boolean isFull) {
        mType.isFull = isFull;
    }

    /**
     * 每行子View的对齐方式
     */
    public final static class Gravite {
        public final static int RIGHT = 0;
        public final static int LEFT = 1;
        public final static int CENTER = 2;
    }
}

自定义属性以及默认样式

    <!--自动换行的LinearLayout !-->
    <declare-styleable name="WarpLinearLayout">
        <attr name="grivate" format="enum"><!--对齐方式 !-->
            <enum name="right" value="0"></enum>
            <enum name="left" value="1"></enum>
            <enum name="center" value="2"></enum>
        </attr>
        <attr name="horizontal_Space" format="dimension"></attr>
        <attr name="vertical_Space" format="dimension"></attr>
        <attr name="isFull" format="boolean"></attr>
    </declare-styleable>


    <!--自动换行的LinearLayout !-->
    <style name="WarpLinearLayoutDefault">
        <item name="grivate">left</item>
    </style>
原文地址:https://www.cnblogs.com/lizhanqi/p/14887382.html