Called from layout when this view should assign a size and position to each of its children
onLayout()方法的注释就是安排自己的子View的位置,我们继承View的时候好像很少用到这个玩意。因为只是写一个控件根本不会存在子View的问题。
接手别人的代码有个FlowLayout,搜索的时候出现历史记录的类似的View,但是换行的时候会出现问题。所以觉得可以自己搞个试试。
首先要继承自ViewGroup这个类。直接重写方法,会发现一定要重写一个onLayout的方法。
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
this(context,null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
之前的onMeasure()是对View的测量。这个onLayout()就是对View的位置进行摆放。写个简单的xml
运行之后发现什么画面没有。这是当然的因为我们什么都没搞。那好现在开始填代码,我们在onLayout()方法中摆放控件。
int count = getChildCount() ;
Log.e(TAG, "onLayout: "+count );
for(int i =0 ;i<count;i++){
View child = getChildAt(i);
child.layout(100*i+100,100*i+100,200*i+200,200*i+200);
}
这里是随便选的几个位置放一下,这里的layout四个参数分别是左上右下离父布局的距离。
这样一看这个简单的代码能实现布局好像有点东西的。既然知道了onLayout()的功能了我们就直接一把梭吧,思路:一个View 挨着一个View 当一行View多的放不下的时候自动换行。两个View之间来电间距。
int count = getChildCount() ;
int indexX = 20;
int indexY = 20;
for(int i =0 ;i<count;i++){
View child = getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
if(i==0){
child.layout(indexX,indexY,indexX+width,indexY+height);
}else{
child.layout(indexX,indexY,indexX+width,indexY+height);
}
indexX+=width;
}
}
一把梭哈,先完成两个的View摆放,这个简单没什么问题。直接放第一个View 初始化一个开始位置(20,20)坐标点,然后开始向右边排。运行起来之后尴尬的事情发生了,UI上啥都没有。我们好像少了什么,一般都要先Measure的吧.ViewGroup中onMeasure主要是来调用measureChildren()方法。通过测量子View的大小然后在自己的View中设置宽高。加上方法,这里其实还有measureChildWithMargins(),这个可以获取到margins。先简单的设置一波。(这里其实要算子View的宽高的总和,再来设置FlowLayout的大小)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
实现效果如:
现在要设置两个View之间的间距,我们可以加一个属性来实现,现在改变一下代码,中间的间隔就出来了。
indexX=indexX+width+padding,下面得看一下换行的效果,思路:当一行容不下要添加的View的时候我们要进行换行,即A+B >ScreenWidth------->height+Y;还是直接走代码吧:
int count = getChildCount() ;
int indexX = 20;
int indexY = 20;
for(int i =0 ;i<count;i++){
View child = getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
if(width+indexX > getMeasuredWidth()){
indexX = 20;
indexY =indexY+ getChildAt(i-1).getMeasuredHeight()+padding;
}
child.layout(indexX,indexY,indexX+width,indexY+height);
indexX=indexX+width+padding;
}
简单的代码就实现了,历史纪录的View,我们可以想想这里是通过xml中自己写的,那岂不是很傻,有时间写好像不如自己把东西画出来,当然当Item数量不固定的时候,好像也挺尴尬的。
-
好吧我们进行第一次改良,需求设计成动态配置View的数量。
public void addViewWithString(ArrayList<String> list){ this.removeAllViews(); for(String string:list) { TextView textView = new TextView(mContext); textView.setText(string); this.addView(textView); } }
首先移除之前的View,然后把TextView添加上去 ,实现效果
这样看好丑,人家的TextView至少还有背景,这个动态添加的背景怎么搞,当然是在setTextView后面加上setBackground属性啦。当然最好是开一个属性在FlowLayout这个控件上面,让写布局的时候可以进行配置。
但是问题来了,显示没毛病我要点击怎么办?设置一个点击事件吧,我们把方法改进一下直接上代码:
public void initDate(ArrayList<String> list){
this.removeAllViews();
for(int i = 0;i<list.size();i++){
addViewWithString(list.get(i),i);
}
}
public void addViewWithString(final String name , final int position){
TextView textView = new TextView(mContext);
textView.setText(name);
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(iChildClickListener!=null){
iChildClickListener.childClickListener(name,position);
}
}
});
this.addView(textView);
}
interface IChildClickListener{
void childClickListener(String name,int position);
}
IChildClickListener iChildClickListener ;
public void setiChildClickListener(IChildClickListener listener){
this.iChildClickListener = listener;
}
外部调用:
FlowLayout flowLayout = findViewById(R.id.flowLayout);
flowLayout.initDate(list);
flowLayout.setiChildClickListener(new FlowLayout.IChildClickListener() {
@Override
public void childClickListener(String name, int position) {
Toast.makeText(MainActivity.this,"第"+position+"个"+"--->"+name,Toast.LENGTH_LONG).show();
}
});
那么就可以实现点击事件以及效果了。好了,把代码整理一下加上背景。