1. android自定义view (UI组件)
1.1 自定义view的简介
1.1.1 为什么要自定义view
在
Android
开发中有很多业务场景,原生的控件是无法满足应用,并且经常也会遇到一个
UI
在多处
重复使用情况,那么就需要通过自定义
View
的方式来实现这些
UI
效果。
作为一个
Android
开发工程师自定义
View
属于一个必备技能。
1.1.2 android自定义view几种方式
自定义 View 的实现方式有以下几种: 组合控件,继承控件,自绘控件详细可分为: 自定义组合控件,继承系统 View 控件,继承系统 ViewGroup ,自绘 View 控件,自绘ViewGroup 控件
1.2 自定义组合控件
组合控件就是将多个控件组合成一个新的控件,可以重复使用。应用场景:在项目中经常会遇到一些比较复杂的 UI 块需要用在 多处使用 ,那么我们就可以通过五大布局 和基本控件组合成一个新的布局View ,这样就可以方便的将该 UI 用在项目的不同页面中,比如一个标题 栏。这种方式比较简单,只要通过布局文件实现相应的UI ,然后将该 UI 加到适合的五大布局中即可。
1.2.1 自定义组合控件的使用步骤
1. 编写布局文件2. 实现构造方法3. 初始化 UI4. 提供对外的方法5. 在布局当中引用该控件6. activity 中使用
1.2.2 示例
中间是
title
的文字,左边是返回按钮
1.
编写布局文件
view_header.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#8E8D8D"
android:id="@+id/rl">
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="8dp"
android:visibility="visible"
app:srcCompat="@drawable/ic_baseline_arrow_back_24" />
<TextView
android:layout_centerInParent="true"
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:textColor="@color/black"
android:text="聊天室" />
</RelativeLayout>
2.
实现构造方法
3.
初始化
UI
4.
提供对外的方法
package com.hp.demo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
//因为我们的布局采用RelativeLayout,所以这里继承RelativeLayout。
public class HeadView extends RelativeLayout {
private RelativeLayout rl;
private ImageView iv;
private TextView tv;
public HeadView(Context context) {
super(context);
}
public HeadView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
//初始化UI,可根据业务需求设置默认值。
private void initView(Context context) {
LayoutInflater.from(context).inflate(R.layout.head_view,this,true);
}
public HeadView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public HeadView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
5.
在布局当中引用该控件
<?xml version="1.0" encoding="utf-8"?>
<com.hp.demo.ForegroundLinearLayout 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="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.hp.demo.HeadView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.hp.demo.ForegroundLinearLayout>
6. activity
中使用
package com.hp.demo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
1.3 继承系统控件
通过继承系统控件(View子类控件或ViewGroup子类控件)来完成自定义View,一般是希望在原 有系统控件基础上做一些修饰性的修改,而不会做大幅度的改动,如在TextView的文字下方添加下 划线,在LinearLayout布局中加一个蒙板等。这种方式往往都会复用系统控件的onMeasure和 onLayout方法,而只需要重写onDraw方法,在其中绘制一些需要的内容。
1.3.1 继承View类系统控件
1.3.1.1
继承
View
类系统控件使用步骤
1. 继承
View
控件,并重写
onDraw
方法
2. 在布局文件中调用
示例:
1.
继承
View
控件,并重写
onDraw
方法
package com.hp.demo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class UnderlineTextView extends androidx.appcompat.widget.AppCompatTextView {
public UnderlineTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint=new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(5);
int width=getWidth();
int height=getBaseline();
canvas.drawLine(0,height,width,height,paint);
}
}
2.
在布局文件中调用
就像使用一个普通TextView
一样使用
UnderlineTextView
。
<?xml version="1.0" encoding="utf-8"?>
<com.hp.demo.ForegroundLinearLayout 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="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.hp.demo.UnderlineTextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:text="继承TextView"
android:textSize="24sp"/>
</com.hp.demo.ForegroundLinearLayout>
1.3.2 继承ViewGroup类系统控件
1.3.2.1 继承ViewGroup类系统控件使用步骤
1. 继承
ViewGroup
类系统控件
2. 在布局文件中调用
示例:
1.
继承
ViewGroup
类系统控件
package com.hp.demo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
public class ForegroundLinearLayout extends LinearLayout {
public ForegroundLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.drawColor(Color.parseColor("#70E1DFDF"));
}
}
2.
在布局文件中调用
对ForegroundLinearLayout
的使用,就和使用其父类
LinearLayout
一样。
<?xml version="1.0" encoding="utf-8"?>
<com.hp.demo.ForegroundLinearLayout 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="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.hp.demo.HeadView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.hp.demo.UnderlineTextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:text="继承TextView"
android:textSize="24sp"/>
</com.hp.demo.ForegroundLinearLayout>
1.3.3 继承系统控件小结
从上面两个例子可见,继承系统原有的控件来实现自定义 View ,步骤非常简单,比组合控件简单多了。但是这一节需要对 Canvas , paint , Path 等绘制方面的知识有一定的了解,且还需要对 ViewGroup 的中内容的绘制顺序有一定的了解,才能在原生控件的基础上做出想要的效果来。
2.三个绘图工具类详解
绘图相关的 一些 API ,他们分别是 Canvas( 画布 ) , Paint( 画笔 ) , Path( 路径 ) ,同时也是自定义View 的基础。
2.1 相关方法详解
2.1.1 Paint(画笔):
2.1.2 Canvas(画布):
2.1.3 Path(路径)
2.2 示例
第一步:
package com.hp.demo;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.Random;
public class MyView extends View {
private Paint paint;
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
paint=new Paint();
paint.setAntiAlias(true);//抗锯齿
paint.setColor(getResources().getColor(R.color.red));//画笔颜色
paint.setStyle(Paint.Style.FILL);//画笔风格 FILL表示实心 STROKE表示空心
paint.setTextSize(36);//绘制文字大小,单位px
paint.setStrokeWidth(5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(getResources().getColor(R.color.white)); //设置画布背景颜色
canvas.drawCircle(getWidth()/2, getHeight()/2, 100, paint); //画实心圆
//canvas.drawRect(100, 50, 300, 150, paint); //画矩形
//绘制Bitmap
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(),R.mipmap.zuan),700,500,paint);//放图片
Paint paint1=new Paint();
paint1.setColor(getResources().getColor(R.color.black));
paint1.setTextSize(36);//绘制文字大小,单位px
paint1.setTextAlign(Paint.Align.CENTER);
float baseLineY=getHeight()/2+Math.abs(paint.ascent()+paint.descent())/2;
canvas.drawText("小日子岛",getWidth()/2,baseLineY,paint1); //绘制文字
//canvas.drawRoundRect(new RectF(60,200,260,310),50,50,paint); //画圆角矩形
//绘制多边形
Path path=new Path();
// path.moveTo(10,10);
// path.lineTo(100,50);
// path.lineTo(200,40);
// path.lineTo(300,20);
// path.lineTo(200,10);
// path.lineTo(100,70);
// path.lineTo(50,40);
// path.close();
// canvas.drawPath(path,paint);
//绘制文字
path.moveTo(0,100);
path.lineTo(100, 0);
path.lineTo(200, 100);
path.lineTo(300, 200);
path.lineTo(400, 100);
path.close();
canvas.drawTextOnPath("ABCDEFGHIJKLMNOPQRSTUVWXYZ", path, 25, 25, paint);
}
}
第二步:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="match_parent"
android:background="#838181"
tools:context=".MainActivity2">
<com.hp.demo.MyView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>