自定义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>