什么是自定义控件
-
原生控件:SDK已经有,Google提供
-
自定义控件: 开发者自己开发的控件,分三种
a. 组合式控件:将现有控件进行组合,实现功能更加强大控件;
b. 继承现有控件: 对其控件的功能进行拓展;
c. 重写View实现全新的控件.
为什么要自定义View?
1.原有控件无法满足我们的需求,所以需要自己实现想要的效果
实现步骤:
a.继承布局,重写构造;
b.创建布局xml,加入到自定义控件里面;
c.实现对应的功能.
实现:
1. onMeasure()用于测量子控件的宽高
2. onLayout()用于摆放子控件在父控件中的位置,只有ViewGroup才能让子控件显示在自己的什么位置.只会触发,执行一次
3. onDraw() 用于绘制需要的图形
Xml布局
<com.example.lenovo.zm0904_textview.views.TagsLayout
android:id="@+id/tag_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:tagHorizontalSpace="10dp"
app:tagVerticalSpace="10dp"
tools:ignore="MissingConstraints">
</com.example.lenovo.zm0904_textview.views.TagsLayout>
其中的TagsLayout就是自己绘制的
首先要创建一个类,继承ViewGroup
TagsLayout的编写
package com.example.lenovo.zm0904_textview.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.example.lenovo.zm0904_textview.R;
public class TagsLayout extends ViewGroup {
private int childHorizontalSpace;
private int childVerticalSpace;
public TagsLayout(Context context) {
super(context);
}
public TagsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//动态获取Style中的属性值
TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.TagsLayout);
if(attrArray != null){
childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagHorizontalSpace,0);
childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagVerticalSpace,0);
attrArray.recycle();
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取它的父容器为它设置的测量模式和大小
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//如果是warp_content情况下,记录宽和高
int width=0,height = 0;
//记录每一行的宽度,width不断取得最大宽度
int lineWidth=0,lineHeight = 0;
int count = getChildCount();
int left = getPaddingLeft();
int top = getPaddingTop();
//遍历每个子元素
for(int i=0; i<count; i++){
View child = getChildAt(i);
if(child.getVisibility() == GONE) continue;
//测量每一个child的宽和高
measureChild(child,widthMeasureSpec,heightMeasureSpec);
//得到child的属性
MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
//当前控件的实际占据宽高
int childWidth = child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin+childHorizontalSpace;
//获取控件实际占据的高度
int childHeight = child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin+childVerticalSpace;
/**
* 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
*/
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
width = Math.max(lineWidth, childWidth);// 取最大的
lineWidth = childWidth; // 重新开启新行,开始记录
// 叠加当前高度,
height += lineHeight;
// 开启记录下一行的高度
lineHeight = childHeight;
child.setTag(new Location(left, top + height, childWidth + left - childHorizontalSpace, height + child.getMeasuredHeight() + top));
} else {
// 否则累加值lineWidth,lineHeight取最大高度
child.setTag(new Location(lineWidth + left, top + height, lineWidth + childWidth - childHorizontalSpace + left, height + child.getMeasuredHeight() + top));
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
}
//计算当前对象的大小
width = Math.max(width, lineWidth) + getPaddingLeft() + getPaddingRight();
height += lineHeight;
sizeHeight += getPaddingTop() + getPaddingBottom();
height += getPaddingTop() + getPaddingBottom();
setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height);
}
//布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//计算子布局中的子元素的位置和大小
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
continue;
Location location = (Location) child.getTag();
child.layout(location.left, location.top, location.right, location.bottom);
}
}
/**
* 记录子控件的坐标
*/
public class Location {
public Location(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public int left;
public int top;
public int right;
public int bottom;
}
}
MainActivity的编写
package com.example.lenovo.zm0904_textview;
import android.graphics.Color;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.lenovo.zm0904_textview.views.TagsLayout;
/**
* Created by Lenovo on 2019/9/4.
*/
public class View1Activity extends AppCompatActivity {
private TagsLayout tagsLayout;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.activity_view1);
initView();
}
private void initView() {
tagsLayout = findViewById(R.id.tag_layout);
ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
//这是由自己写的,里面的内容可以更改
String[] strs = {"这就是一个自定义组件","android入门","你是一个程序猿","java","python从入门到放弃"};
for(int i=0; i< strs.length; i++){
TextView txt = new TextView(this);
txt.setText(strs[i]);
Log.i("tag", "initView: "+strs[i]);
txt.setTextColor(Color.WHITE);
txt.setBackgroundResource(R.drawable.txt_bg);
tagsLayout.addView(txt,lp);
}
}
}
最后的效果图:

网友评论