初探自定义控件
一般来说把自定义控件分为3中:1、自绘控件(自己绘制各种图形) 2、组合控件(系统已有控件组合到一起) 3、继承控件(对系统已有控件功能扩展)
首先了解view绘制的三个主要流程,Measure----Layout----Draw
View先测量大小,对应的方法是onMeasure();设置子控件的位置,对应方法是onLayout();绘制图形,对应的方法是onDraw();
一、自绘控件
绘制一个简单的图形: 一个太极图标
首先新建类继承View,重写起构造方法,与我们紧密相关的主要有三个方法:onMeasure(),draw(),onTouchEvent();
在onMeasure方法中主要绘制控件的大小,在这个方法中可以设置空间的大小,其中需要注意的是MeasureSpec.getSize()方法,可以获取控件的大小。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec);//设置宽度与高度相等 viewWidth=MeasureSpec.getSize(widthMeasureSpec);//控件的宽度 指的是像素 Log.e("myview", "onMeasure: ----size----" + MeasureSpec.getSize(widthMeasureSpec) + "----mode---" + MeasureSpec.getMode(widthMeasureSpec)); }
在draw()方法中绘制图形,绘制图形主要有Paint(画笔),Canvas(画布)两个类完成的:
有两种黑白颜色的画笔,定义一个创建得到画笔的方法:
private Paint getPaint(int color){ Paint paint=new Paint(); paint=new Paint(); paint.setAntiAlias(true);////设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。 paint.setDither(true);//设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰 /* * 置画笔样式,如果不设置,默认是全部填充(FILL)。可选项为:FILL,FILL_OR_STROKE,或STROKE 画笔样式分三种: 1.Paint.Style.STROKE:描边 2.Paint.Style.FILL_AND_STROKE:描边并填充 3.Paint.Style.FILL:填充 * */ paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setColor(color); return paint; }然后再onDraw()方法中绘制图形
@Override public void draw(Canvas canvas) { super.draw(canvas); int srccnWidth=viewWidth; //控制椭圆大小及位置的矩形坐标 对应的是左上 右下坐标 这里表示的一个正方形 绘制出来的是一个圆形圆弧 RectF rect=new RectF(0,0,srccnWidth,srccnWidth); //左侧黑色圆弧 canvas.drawArc(rect,90,180,false,mArcPaintBlack);//false 表示只绘制一个圆弧 true表示 圆弧将会闭合 会多出来一条线 //右侧白色圆弧 canvas.drawArc(rect,-90,180,false,mArcPaintWhite); //上方黑色小圆弧 RectF rect2=new RectF(srccnWidth/4,0,srccnWidth/4*3,srccnWidth/2); canvas.drawArc(rect2,-90,180,false,mArcPaintBlack); //下方白色小圆弧 RectF rect3=new RectF(srccnWidth/4,srccnWidth/2,srccnWidth/4*3,srccnWidth); canvas.drawArc(rect3,90,180,false,mArcPaintWhite); //黑色圆圈 canvas.drawCircle(srccnWidth/2,srccnWidth/4,srccnWidth/16,mArcPaintWhite); //白色圆圈 canvas.drawCircle(srccnWidth/2,srccnWidth/4*3,srccnWidth/16,mArcPaintBlack); }
在自定义控件中添加自定义的属性(如控制画笔的颜色):
在valus目录下创建attrs.xml文件
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="myView"> <attr name="lbb_painColor" format="color|reference"/> <attr name="lbb_lineWeigh" format="dimension"/> </declare-styleable> </resources>name="lbb_viewbg" 自定义属性名字 format="color|reference" 属性类型
format各种值的含义:
reference:引用资源string:字符串
Color:颜色
boolean:布尔值
dimension:尺寸值
float:浮点型
integer:整型
fraction:百分数
enum:枚举类型
flag:位或运算
在自定义控件中获取属性:
public MyView2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getProperties(attrs);//在构造方法执行 } //获取自定义属性的值 private void getProperties(AttributeSet attrs){ TypedArray typedArray=getContext().obtainStyledAttributes(attrs, R.styleable.myView); colorWhite=typedArray.getColor(R.styleable.myView_lbb_painColor, colorWhite);//获取画笔颜色 属性 mLineStrokeWidth=typedArray.getDimension(R.styleable.myView_lbb_lineWeigh, mLineStrokeWidth);//获取画笔大小 typedArray.recycle(); mArcPaintBlack=getPaint(colorBlack); mArcPaintWhite=getPaint(colorWhite);//给黑色画笔设置颜色 }
在xml中定义属性:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:lbb="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:layout_margin="20dp" > <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.teemo.myview_demo.myView.MyView2 android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#00ff00" lbb:lbb_painColor="#ffff00" /> <View android:layout_width="match_parent" android:layout_height="20dp"/> <com.example.teemo.myview_demo.myView.MyView1 android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ff0000" /> </LinearLayout> </ScrollView> </LinearLayout>
在布局文件中根控件中添加
xmlns:lbb="http://schemas.android.com/apk/res-auto" 其中lbb自己任意定义 据说添加这个后Gradle自动查找自定义的属性(没有深入研究)
在自定义控件MyView2中添加属性
lbb:lbb_painColor="#ffff00" 这时白色的画笔会变为我们设置的颜色
另外有有一点,当绘制线条时,如果线条很粗,要考虑我们定义的画笔开始/结束的坐标位于线条的中心,如当我们定义的线条宽度为80f时,在空间左边缘竖直画一条线时,其宽度只有40f,
以为还有一半超出了控件边界。如
canvas.drawLine(0,0,0,mLineStrokeWidth,mLinePaint);
可以改为 canvas.drawLine(40,0,40,mLineStrokeWidth,mLinePaint);
这个是画万字符号时遇到的问题
二 、组合控件
组合控件只要把系统已有的控件组合起来即可,实现相对复杂的布局,也可以把常用到的布局封装起来。
以linearlayout 中嵌套 TextView 和 Recyclerview 为例,其效果如下
首先定义一个类在其内部实现布局:
/** * Created by lianbinbo on 2016/12/20. */ public class MyViewHolder extends RelativeLayout { private Context mContext; private RecyclerView mRecyclerView; private TextView mTextView; private Adapter_view3 mAdapter_view3; public MyViewHolder(Context context) { super(context); this.mContext=context; initView(); } private void initView(){ View view= LayoutInflater.from(mContext).inflate(R.layout.myview3,this);//要实现的布局 mRecyclerView= (RecyclerView) view.findViewById(R.id.rv_item); mTextView= (TextView) view.findViewById(R.id.tv_title); GridLayoutManager gridLayoutManager=new GridLayoutManager(mContext,2);//实现recycleview为网格形式 if (mAdapter_view3==null){ mAdapter_view3=new Adapter_view3(mContext); } if (mRecyclerView!=null){ mRecyclerView.setLayoutManager(gridLayoutManager); mRecyclerView.setHasFixedSize(true); mRecyclerView.setAdapter(mAdapter_view3); } } public void addItem(List<ItemModle> itemModle,int pos){//得到数据 if (mAdapter_view3!=null){ mAdapter_view3.updateItems(itemModle);//更新界面 Log.e("myView3", "myViewHolder: -----" + itemModle.size() + "---pos---" + pos); } if (mTextView!=null){//如果要增加列表 直接增加数据 在这里修改列表头 在适配器 判断显示不同数据 if (pos==0){ mTextView.setText("质态"); }else { mTextView.setText("资源"); } } } }
该demo使用该自定义布局是在Recycleview中,在一个Activity中定义一个RecyclerView,然后把该自定义控件加载到Recycleview中,可以实现上面的排列效果,如果要增加排列的数量,直接控制数据就可以了。
Activity 中布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.teemo.myview_demo.Main2Activity"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_main2Activity" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
Activity中RecyclerView中的适配器:
/** * Created by lianbinbo on 2016/12/20. */ public class MyAdapter_main2 extends BaseRecyclerAdapter<MyModle1> { public MyAdapter_main2(Context mContext) { super(mContext); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder; viewHolder=new ViewHolderMain2(new MyViewHolder(mContext)); return viewHolder; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ViewHolderMain2 viewHolderMain2= (ViewHolderMain2) holder; //把数据传到定义的控件中 viewHolderMain2.myViewHolder.addItem(mItem.get(position).getItemModleList(),position); Log.e("myView3", "onBindViewHolder--1--: --position---"+position+"---size---"+mItem.size() ); } public class ViewHolderMain2 extends RecyclerView.ViewHolder{ MyViewHolder myViewHolder;//自己定义的控件 public ViewHolderMain2(View itemView) { super(itemView); myViewHolder= (MyViewHolder) itemView; } } }
自定义控件中的适配器:
/** * Created by lianbinbo on 2016/12/20. */ public class Adapter_view3 extends BaseRecyclerAdapter<ItemModle> { public Adapter_view3(Context mContext) { super(mContext); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolderView3(LayoutInflater.from(mContext).inflate(R.layout.myview3_item,parent,false)); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ViewHolderView3 viewHolderView3= (ViewHolderView3) holder; ItemModle itemModle=getItem(position); if ("0".equals(itemModle.getTag())){//根据 传入的标记 显示不同的图片 viewHolderView3.iv.setImageResource(R.drawable.home_grid_04); }else if ("1".equals(itemModle.getTag())){ viewHolderView3.iv.setImageResource(R.drawable.home_grid_06); } viewHolderView3.textView.setText(itemModle.getName()); Log.e("myView3", "onBindViewHolder--2--: ---position--" + position + "---size---" + mItem.size()); } public class ViewHolderView3 extends RecyclerView.ViewHolder{ private ImageView iv; private TextView textView; public ViewHolderView3(View itemView) { super(itemView); iv= (ImageView) itemView.findViewById(R.id.iv); textView= (TextView) itemView.findViewById(R.id.tv_item); } } }
这样把控件封装到一起,有类似的布局就可以复用:而且在Recycleview中用该控件,可以轻松实现有连续相似布局的效果。像一些视频APP节目的展示:
三,继承控件,这个不多说了,直接在网上复制一个代码,实现禁止GridView滑动的效果
public class GridViewInScrollView extends GridView { public GridViewInScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public GridViewInScrollView(Context context) { super(context); } public GridViewInScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } }