前言
前面viewGroup文章简单阐述了自定义viewGroup需要注意的事项,但是过于简单,实战性不强,导致对viewGroup理解的比较浅显,所以这篇文章主要是结合实战例子进行讲解。
深化的自定义viewgroup布局流程
- 1.自定义属性:声明、设置、解析获取自定义值
- 2.在onmeasure()测量自身、child宽高
- 3.在onlayout() 根据自己规则确认children位置
- 4.绘制ondraw()
- 5.处理layoutparams
- 6.触摸反馈:滑动事件
实现FlowLayout思路
- 1.一行能放几个?
- 2.怎么摆放子view,每行摆放完后怎么换行?
- 3.一个屏幕不够放置子view的话,如何实现滑动?以及怎么处理滑动冲突?
- 4.整个可滑动的区域可滑动最大区域怎么设定?总不能可以一直下滑、上滑,导致控件不在屏幕中 基本上解决了上面的问题,整个控件就达到了我们的要求。
问题一:一行能放几个?
前面我们讲到,测量控件大小 核心方法就是onMeasure(),只有测量出每个控件的大小,我们才能知道每行最多可以放几个控件。我就直接贴代码了
/**
* 第一步先测量子控件大小,测量完以后可以设置当前控件的大小了。
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//todo 这个是调用父类(ViewGroup)测量模式 设置大小 为啥需要重写呢
// 1.因为当父类对当前控件不做限制的话 当前控件高度默认是非常小的
// 2.父类给的测量模式是match/wrap_content的时候 ,子类默认跟父类一样 实际上可能没有这么大
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//父类提供给当前的测量模式跟大小(不代表最终的大小)
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//所以需要重新测量子类大小
int childCount = getChildCount();
if (childCount == 0) {
return;
}
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//1. measureChildren();里面会遍历调用measureChild()方法
//2.measureChild();参数一是要测量的view 参数二跟参数三是传入父类的MeasureSpec
//todo 重点一 :measureChild分析下
//这边注意一种场景 :父类采用Exactly,子类布局采用的是match_parent模式 那就会导致当前view占满屏,所以此处需要针对这种情况设置一个固定值
//都是针对view的
//todo 重点二:(这种写法不行)if (child.getLayoutParams().height == LayoutParams.MATCH_PARENT) { 这种写法是对getChildMeasureSpec研究不正确导致的
// LayoutParams layoutParams = child.getLayoutParams();
// int childMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, layoutParams.width);
// //todo 暂时给死高度100px
// int childMeasureSpec1 = getChildMeasureSpec(widthMeasureSpec, 0,100);
// child.measure(childMeasureSpec,childMeasureSpec1);
//} else {
// measureChild(child, widthMeasureSpec, heightMeasureSpec);
//}
//需要换种思路
measureChild(child, widthMeasureSpec, heightMeasureSpec);
if (child.getMeasuredHeight() == heightSize) {
LayoutParams layoutParams = child.getLayoutParams();
int childMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, layoutParams.width);
//todo 暂时给死高度100px 这种写法虽然基本上解决问题了 但是有个弊端 必须给死高度 还可以采用当前行高度
int childMeasureSpec1 = getChildMeasureSpec(widthMeasureSpec, 0, 100);
child.measure(childMeasureSpec, childMeasureSpec1);
}
}
//测量完以后就开始计算当前控件的大小
//思路流程:1.因为是流式布局 那么宽度是最大行的宽度,高度是每行高度之和,
// 2.有些行数默认采用的是跟父控件一样的高度 那就会导致某行直接撑满屏幕的问题,这种需要设置一个固定高度/或者去当前行最大行的高度为这个子view的高度
// 3.还需要考虑多少个view以后换行的问题
// 3.测量完以后需要通过onLayout进行摆放,所以需要保存所有的view
//当前行的高度
int curLineHeight = 0;
//当前行宽度
int curLineWidth = 0;
//最大行宽度
int maxLineWidth = 0;
//所有行高度
int totalLineHeight = 0;
// 当前行的view集合
List<View> curlineViews = new ArrayList<>();
//所有行view的集合
totalLineViews = new ArrayList<>();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//当前不需要换行
if (curLineWidth + child.getMeasuredWidth() < widthSize) {
curLineWidth = curLineWidth + child.getMeasuredWidth();
curLineHeight = Math.max(curLineHeight, child.getMeasuredHeight());
curlineViews.add(child);
} else {
//换行
maxLineWidth = Math.max(maxLineWidth, curLineWidth);
curLineWidth = 0;
totalLineHeight = totalLineHeight + curLineHeight;
curLineHeight = 0;
totalLineViews.add(curlineViews);
//不能调用clear方法 会导致totalLineViews里面view被清空
curlineViews = new ArrayList<>();
}
}
//注意:循环外 上面代码忽略了如果有最后一行的话 是没有添加进去的,因为最后一行走了if语句 但是永远走不到else里面 也就没有进行计算
// 添加最后一行 其实就是再走一遍else方法
maxLineWidth = Math.max(maxLineWidth, curLineWidth);
curLineWidth = 0;
totalLineHeight = totalLineHeight + curLineHeight;
curLineHeight = 0;
totalLineViews.add(curlineViews);
//不能调用clear方法 会导致totalLineViews里面view被清空
curlineViews = new ArrayList<>();
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY?widthSize:maxLineWidth, heightMode == MeasureSpec.EXACTLY?heightSize:totalLineHeight);
}
问题二:怎么摆放子view,每行摆放完后怎么换行?
这个就涉及到onLayout()方法了,也是直接贴代码吧。
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//思路流程: 按行摆放
if (totalLineViews.size() <= 0) {
return;
}
int left = 0;
int top = 0;
//当前行高最大值
int curLineMaxHeight = 0;
for (int i = 0; i < totalLineViews.size(); i++) {
//当前行view 集合
List<View> curViews = totalLineViews.get(i);
if (curViews.size() <= 0) {
continue;
}
//初始化每行左边位置
left = 0;
for (int j = 0; j < curViews.size(); j++) {
View view = curViews.get(j);
view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
left = left + view.getMeasuredWidth();
//获取当前行最大高度
curLineMaxHeight = Math.max(curLineMaxHeight, view.getMeasuredHeight());
}
//为下一行高度赋初始值
top = top + curLineMaxHeight;
curLineMaxHeight = 0;
}
}
问题3:一个屏幕不够放置子view的话,如何实现滑动?以及怎么处理滑动冲突?
这里面其实难点是事件滑动的过程中事件冲突、事件拦截的处理,可参考我之前的文章事件分发源码分析,我们这边主要是处理viewGroup的事件滑动,所以activity、view的滑动你们可以暂时忽略,注意!如果事件滑动掌握不了,就不建议往下看了。
效果明天放上来。不知道怎么放gif/视频
首先考虑什么时候拦截,上代码
public FlowLayoutNew(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* 初始化
*
* @param context
* @param attrs
*/
private void init(Context context, AttributeSet attrs) {
ViewConfiguration configuration = ViewConfiguration.get(context);
//获取最小滑动距离
scaledPagingTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
//如果想滑动还需要设置
setClickable(true);
}
--------------------------------------------------
private float downX;
private float downY;
//1.考虑什么时候拦截
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean isIntercept = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
isIntercept = false;
break;
case MotionEvent.ACTION_MOVE:
//
float offsetX = event.getX() - downX;
float offsetY = event.getY() - downY;
if (Math.abs(offsetY) > Math.abs(offsetX) && Math.abs(offsetY) > scaledPagingTouchSlop) {
isIntercept = true;
} else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
default:
isIntercept = false;
break;
}
return isIntercept;
}
拦截完后怎么处理滑动(其实这里面也解答了问题四)
//todo 这个方法多久刷新一次
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!canScrollable) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//重点:注意:当motionEvent事件状态是ACTION_DOWN,当前控件是不拦截的
// 所以导致onTouchEvent的是不存在MotionEvent.ACTION_DOWN状态的,因为此处赋值是不生效的
break;
case MotionEvent.ACTION_MOVE:
// 只有isIntercept= true 的状况下,才会走这里面的代码
float offsetY = downY - event.getY();
//注意当offsetY大于0的时候,说明期待滑动的效果是上滑,scrollTo 会调用invalidate()方法 然后到draw方法 再到canvas.translate(mLeft - sx, mTop - sy);
//sx值是scrollTo方法中的参数一,sy值是方法的参数二 scrollTo 其实移动的是画布
float dy = getScrollY() + offsetY;
//最上面不能留白
if (dy<0) {
dy = 0;
}
//最下面也不能留白
if (dy>(realHeight-getMeasuredHeight())) {
dy = realHeight-getMeasuredHeight();
}
// todo 重点:
//方式一:
scrollTo(getScrollX(), (int) dy);
//方式二:
//注意: 这个地方一定需要重新赋值 因为downY只有在按下的那时候才会赋值
downY = event.getY();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
default:
break;
}
return super.onTouchEvent(event);
}
通过其他方式处理滑动效果
上面代码讲了通过scrollTo方法处理滑动,但是这种处理效果会不太丝滑,就是一顿一顿的。
scroller = new Scroller(context);
---------------------------------------
//方式二:
// scroller.startScroll方法 通过提供起点和行进距离开始滚动。 *滚动将在*持续时间内使用默认值250毫秒
//startx 起始水平滚动偏移量(以像素为单位)。正*号会将内容向左滚动
//dx x行驶的水平距离。正数会将*内容向左滚动
//scroller.getFinalY() 最终的Y偏移量是距原点的绝对距离。
//跟scrollTo方法的区别 https://blog.csdn.net/smile_Running/article/details/81635279
scroller.startScroll(0,getScrollY(),0, (int) offsetY);
//注意: 这个地方一定需要重新赋值 因为downY只有在按下的那时候才会赋值
downY = event.getY();
invalidate();
------------------------------------
@Override
public void computeScroll() {
super.computeScroll();
//判断是否在滑动 返回true 表示动画还没结束
if (scroller.computeScrollOffset()) {
//返回滚动中的当前Y偏移量。它是距原点的绝对距离
int currY = scroller.getCurrY();
if(currY < 0){
currY = 0;
}
if(currY > realHeight - getMeasuredHeight()){
currY = realHeight - getMeasuredHeight();
}
scrollTo(0,currY );
postInvalidate();
}
}
//setBackgroundResource(R.drawable.search_bg); //int defaultWidth = getContext().getResources() // .getDimensionPixelOffset(R.dimen.dp_290); //int defaultHeight = getContext().getResources() // .getDimensionPixelOffset(R.dimen.dp_30); // setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,getd)); 跟下面区别在那 // www.jianshu.com/p/36b200a0b… //setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); //setLayoutParams(new LayoutParams(defaultWidth, defaultHeight)); setOrientation(HORIZONTAL); //setBackgroundResource(R.drawable.search_bg);
项目中SearchLayout使用 主要是LayoutParams使用 还有getlayoutparam()获取的是什么
new LinearLayout.LayoutParams跟new LayoutParams区别
https://www.jianshu.com/p/0d6f753fdd92
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1202/668.html
https://www.jianshu.com/p/36b200a0bff4
知识储备
1.Scroller类中startScroll()、computeScroll()方法、scrollTo()和scrollBy()方法 Scroller的使用及解析
2.事件分发 onInterceptTouchEvent onTouchEvent 可以参考我之前的文章
3.onmeasure onlayout 主要是viewGroup的事件分发
4.
scroller类(为了滑动流畅) 涉及到
mstartX:滑动时起点偏移坐标
mFInalx:滑动完成后的偏移坐标
mcurrX:滑动过程中,根据fun计算当前的滑动偏移距离【start-final】
mdeltax:滑动过程中,mfianl-mcurr
方法 startSrcoll主要是做初始化
computeScroll
invalidate 跟postinvalidate区别 :第一个区别就不用说了 postinvalidate封装了hanlder,但是第二个区别就不是很懂,说是如果invalidate没有执行完再次调用invalidate不会执行 而postinvalidate不会,如果哪位老友知道区别,请评论告知 谢谢
完整代码粘贴
1.核心类
package com.xm.wanapp.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import java.util.ArrayList;
import java.util.List;
import androidx.core.view.ViewConfigurationCompat;
public class FlowLayoutNew extends ViewGroup {
/**
* 整个是否可以滑动。
*/
boolean canScrollable = false;
private List<List<View>> totalLineViews;
/**
* 最小滑动距离
*/
private int scaledPagingTouchSlop;
private Scroller scroller;
public FlowLayoutNew(Context context) {
this(context, null);
}
public FlowLayoutNew(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayoutNew(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* 初始化
*
* @param context
* @param attrs
*/
private void init(Context context, AttributeSet attrs) {
ViewConfiguration configuration = ViewConfiguration.get(context);
//获取最小滑动距离
scaledPagingTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
//如果想滑动还需要设置
setClickable(true);
scroller = new Scroller(context);
}
/**
* 第一步先测量子控件大小,测量完以后可以设置当前控件的大小了。
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//todo 这个是调用父类(ViewGroup)测量模式 设置大小 为啥需要重写呢
// 1.因为当父类对当前控件不做限制的话 当前控件高度默认是非常小的
// 2.父类给的测量模式是match/wrap_content的时候 ,子类默认跟父类一样 实际上可能没有这么大
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//父类提供给当前的测量模式跟大小(不代表最终的大小)
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//所以需要重新测量子类大小
int childCount = getChildCount();
if (childCount == 0) {
return;
}
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//1. measureChildren();里面会遍历调用measureChild()方法
//2.measureChild();参数一是要测量的view 参数二跟参数三是传入父类的MeasureSpec
//todo 重点一 :measureChild分析下
//这边注意一种场景 :父类采用Exactly,子类布局采用的是match_parent模式 那就会导致当前view占满屏,所以此处需要针对这种情况设置一个固定值
//都是针对view的
//todo 重点二:(这种写法不行)if (child.getLayoutParams().height == LayoutParams.MATCH_PARENT) { 这种写法是对getChildMeasureSpec研究不正确导致的
// LayoutParams layoutParams = child.getLayoutParams();
// interactions childMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, layoutParams.width);
// //todo 暂时给死高度100px
// int childMeasureSpec1 = getChildMeasureSpec(widthMeasureSpec, 0,100);
// child.measure(childMeasureSpec,childMeasureSpec1);
//} else {
// measureChild(child, widthMeasureSpec, heightMeasureSpec);
//}
//需要换种思路
measureChild(child, widthMeasureSpec, heightMeasureSpec);
if (child.getMeasuredHeight() == heightSize) {
LayoutParams layoutParams = child.getLayoutParams();
int childMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, layoutParams.width);
//todo 暂时给死高度100px 这种写法虽然基本上解决问题了 但是有个弊端 必须给死高度 还可以采用当前行高度
int childMeasureSpec1 = getChildMeasureSpec(widthMeasureSpec, 0, 100);
child.measure(childMeasureSpec, childMeasureSpec1);
}
}
//测量完以后就开始计算当前控件的大小
//思路流程:1.因为是流式布局 那么宽度是最大行的宽度,高度是每行高度之和,
// 2.有些行数默认采用的是跟父控件一样的高度 那就会导致某行直接撑满屏幕的问题,这种需要设置一个固定高度/或者去当前行最大行的高度为这个子view的高度
// 3.还需要考虑多少个view以后换行的问题
// 3.测量完以后需要通过onLayout进行摆放,所以需要保存所有的view
//当前行的高度
int curLineHeight = 0;
//当前行宽度
int curLineWidth = 0;
//最大行宽度
int maxLineWidth = 0;
//所有行高度
int totalLineHeight = 0;
// 当前行的view集合
List<View> curlineViews = new ArrayList<>();
//所有行view的集合
totalLineViews = new ArrayList<>();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//当前不需要换行
if (curLineWidth + child.getMeasuredWidth() < widthSize) {
curLineWidth = curLineWidth + child.getMeasuredWidth();
curLineHeight = Math.max(curLineHeight, child.getMeasuredHeight());
curlineViews.add(child);
} else {
//换行
maxLineWidth = Math.max(maxLineWidth, curLineWidth);
curLineWidth = 0;
totalLineHeight = totalLineHeight + curLineHeight;
curLineHeight = 0;
totalLineViews.add(curlineViews);
//不能调用clear方法 会导致totalLineViews里面view被清空
curlineViews = new ArrayList<>();
}
}
//注意:循环外 上面代码忽略了如果有最后一行的话 是没有添加进去的,因为最后一行走了if语句 但是永远走不到else里面 也就没有进行计算
// 添加最后一行 其实就是再走一遍else方法
maxLineWidth = Math.max(maxLineWidth, curLineWidth);
curLineWidth = 0;
totalLineHeight = totalLineHeight + curLineHeight;
curLineHeight = 0;
totalLineViews.add(curlineViews);
//不能调用clear方法 会导致totalLineViews里面view被清空
curlineViews = new ArrayList<>();
//todo 其实这个地方计算是不准确的
if (heightSize<totalLineHeight) {
canScrollable = true;
}
realHeight = totalLineHeight;
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY?widthSize:maxLineWidth, heightMode == MeasureSpec.EXACTLY?heightSize:totalLineHeight);
}
/**
* 真实内容的高度
*/
int realHeight;
/**
* 计算子view 摆放布局。
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//思路流程: 按行摆放
if (totalLineViews.size() <= 0) {
return;
}
int left = 0;
int top = 0;
//当前行高最大值
int curLineMaxHeight = 0;
for (int i = 0; i < totalLineViews.size(); i++) {
//当前行view 集合
List<View> curViews = totalLineViews.get(i);
if (curViews.size() <= 0) {
continue;
}
//初始化每行左边位置
left = 0;
for (int j = 0; j < curViews.size(); j++) {
View view = curViews.get(j);
view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
left = left + view.getMeasuredWidth();
//获取当前行最大高度
curLineMaxHeight = Math.max(curLineMaxHeight, view.getMeasuredHeight());
}
//为下一行高度赋初始值
top = top + curLineMaxHeight;
curLineMaxHeight = 0;
}
}
private float downX;
private float downY;
//1.考虑什么时候拦截
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean isIntercept = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
isIntercept = false;
break;
case MotionEvent.ACTION_MOVE:
//
float offsetX = event.getX() - downX;
float offsetY = event.getY() - downY;
if (Math.abs(offsetY) > Math.abs(offsetX) && Math.abs(offsetY) > scaledPagingTouchSlop) {
isIntercept = true;
} else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
default:
isIntercept = false;
break;
}
return isIntercept;
}
//todo 这个方法多久刷新一次
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!canScrollable) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//重点:注意:当motionEvent事件状态是ACTION_DOWN,当前控件是不拦截的
// 所以导致onTouchEvent的是不存在MotionEvent.ACTION_DOWN状态的,因为此处赋值是不生效的
break;
case MotionEvent.ACTION_MOVE:
// 只有isIntercept= true 的状况下,才会走这里面的代码
float offsetY = downY - event.getY();
//注意当offsetY大于0的时候,说明期待滑动的效果是上滑,scrollTo 会调用invalidate()方法 然后到draw方法 再到canvas.translate(mLeft - sx, mTop - sy);
//sx值是scrollTo方法中的参数一,sy值是方法的参数二 scrollTo 其实移动的是画布
float dy = getScrollY() + offsetY;
//最上面不能留白
if (dy<0) {
dy = 0;
}
//最下面也不能留白
if (dy>(realHeight-getMeasuredHeight())) {
dy = realHeight-getMeasuredHeight();
}
// todo 重点:
//方式一:
scrollTo(getScrollX(), (int) dy);
//方式二:
// scroller.startScroll方法 通过提供起点和行进距离开始滚动。 *滚动将在*持续时间内使用默认值250毫秒
//startx 起始水平滚动偏移量(以像素为单位)。正*号会将内容向左滚动
//dx x行驶的水平距离。正数会将*内容向左滚动
//scroller.getFinalY() 最终的Y偏移量是距原点的绝对距离。
//跟scrollTo方法的区别 https://blog.csdn.net/smile_Running/article/details/81635279
scroller.startScroll(0,getScrollY(),0, (int) offsetY);
//注意: 这个地方一定需要重新赋值 因为downY只有在按下的那时候才会赋值
downY = event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
super.computeScroll();
//判断是否在滑动 返回true 表示动画还没结束
if (scroller.computeScrollOffset()) {
//返回滚动中的当前Y偏移量。它是距原点的绝对距离
int currY = scroller.getCurrY();
if(currY < 0){
currY = 0;
}
if(currY > realHeight - getMeasuredHeight()){
currY = realHeight - getMeasuredHeight();
}
scrollTo(0,currY );
postInvalidate();
}
}
}
XML文件
<com.xm.wanapp.view.FlowLayoutNew
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="200dp"
tools:context=".MainActivity"
android:background="#40000000">
<Button
android:layout_width="wrap_content"
android:layout_height="55dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="你是谁呀" />
<View
android:layout_width="30dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="90dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="45dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="75dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="65dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="75dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="80dp"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="300dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="65dp"
android:text="人在他在,塔亡人亡"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方1" />
<Button
android:layout_width="wrap_content"
android:layout_height="250dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是结束" />
</com.xm.wanapp.view.FlowLayoutNew>