zoukankan      html  css  js  c++  java
  • 自定义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>
  • 相关阅读:
    CRM 客户线索 与 销售人员
    CRM X
    MySQL为Null导致的5大坑
    搞懂 macOS 上的主机名/hostname/ComputerName
    Node服务中如何写日志?
    Linux下 iptables 超详细教程和使用示例
    精读《Prisma 的使用》
    Redis夺命20问
    redis HyperLogLog的使用
    聊聊redis分布式锁的8大坑
  • 原文地址:https://www.cnblogs.com/lizhanqi/p/14887382.html
Copyright © 2011-2022 走看看