自定view分三种:
1、对已有的控件进行拓展
2、对创建复合控件
3、对重写view实现全新view
(一)对已有的控件进行拓展
1、我们通常回会重写onDraw方法,对已有的控件进行功能拓展,一般是在onDraw方法中完成功能拓展
@Override
protected void onDraw(Canvas canvas) {
//完成功能拓展
super.onDraw(canvas);//完成原生控件的绘制
//或者后面完成功能拓展
}
例子:
@SuppressLint("AppCompatCustomView")
public class MyTextView extends TextView {
private int mViewWidth = 0;
private Paint mPaint;
private LinearGradient mLinearGradient;
private Matrix mGradientMatrix;
private int mTranslate;
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
if (mViewWidth > 0) {
mPaint = getPaint();
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0, new int[]{Color.BLUE, 0xffffffff, Color.BLUE}, null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
mGradientMatrix = new Matrix();
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mGradientMatrix != null) {
mTranslate += mViewWidth / 5;
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mGradientMatrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(mGradientMatrix);
postInvalidateDelayed(100);
}
}
}
(二)复合控件的自定义view
复合控件一般是由几个view组合为一个新的控件,这个控件一般继承viewgroup
步骤:
1、在res资源文件下面新建attr.xml的属性文件,通过该文件进行代码自定义属性
例如:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TopBar">
<!-- 标题内容 -->
<attr name="title_text" format="string"/>
<!-- 标题字体大小 -->
<attr name="title_text_size" format="dimension"/>
<!-- 标题颜色 -->
<attr name="title_text_color" format="color"/>
<!-- 左边按钮的文字 -->
<attr name="left_text" format="string"/>
<!-- 左边按钮文字颜色 -->
<attr name="left_text_color" format="color"/>
<!-- 左边按钮的背景 -->
<attr name="left_bg" format="reference|color"/>
<!-- 右边按钮文字 -->
<attr name="right_text" format="string"/>
<!-- 右边按钮文字颜色 -->
<attr name="right_text_color" format="color"/>
<!-- 右边按钮背景 -->
<attr name="right_bg" format="reference|color"/>
</declare-styleable>
</resources>
2、可以写一个复合控件的xml文件进行复合控件的界面加载:
例如:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="60dp">
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:text="button1"
android:id="@+id/b1"
android:background="#f4ed79"
android:textSize="10sp"
android:gravity="center"/>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="4"
android:text="text"
android:id="@+id/t1"
android:textSize="10sp"
android:background="#79f4e0"
android:gravity="center"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button2"
android:id="@+id/b2"
android:background="#f4ed79"
android:layout_margin="5dp"
android:textSize="10sp"
android:gravity="center"/>
</LinearLayout>
3、开始写复合控件:
一般是先加载xml的界面,再加载attr.xml的属性,再将属性设置到每个view控件上面
//加载界面并绑定每个view
LayoutInflater.from(context).inflate(R.layout.my_view,this);
b1=findViewById(R.id.b1);
b2=findViewById(R.id.b2);
t1=findViewById(R.id.t1);
//获取属性
TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.my_view);
b1_text=ta.getString(R.styleable.my_view_b1_text);
b1_text_size=ta.getDimension(R.styleable.my_view_b1_text_size,0);
b1_text_backgroud=ta.getResourceId(R.styleable.my_view_b1_text_backgroud,R.drawable.ic_launcher_foreground);
b2_text=ta.getString(R.styleable.my_view_b2_text);
b2_text_size=ta.getDimension(R.styleable.my_view_b2_text_size,0);
b2_text_backgroud=ta.getResourceId(R.styleable.my_view_b2_text_backgroud,R.drawable.ic_launcher_foreground);
t1_text=ta.getString(R.styleable.my_view_t1_text);
t1_text_size=ta.getDimension(R.styleable.my_view_t1_text_size,0);
t1_text_backgroud=ta.getResourceId(R.styleable.my_view_t1_text_backgroud,R.drawable.ic_launcher_foreground);
//设置属性
t1.setText(t1_text);
t1.setTextSize(t1_text_size);
t1.setBackgroundResource(t1_text_backgroud);
b1.setText(b1_text);
b1.setTextSize(b1_text_size);
b1.setBackgroundResource(b1_text_backgroud);
b2.setText(b2_text);
b2.setTextSize(b2_text_size);
b2.setBackgroundResource(b2_text_backgroud);
4、设置接口
先新建接口,再将接口暴露给用户
interface Listenr{
public void clickleft();
public void clickright();
}
Listenr listenr;
public void setListenr(Listenr listenr) {
this.listenr = listenr;
}
例子:实现自定义控件:
1、界面的xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="60dp">
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:text="button1"
android:id="@+id/b1"
android:background="#f4ed79"
android:textSize="10sp"
android:gravity="center"/>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="4"
android:text="text"
android:id="@+id/t1"
android:textSize="10sp"
android:background="#79f4e0"
android:gravity="center"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button2"
android:id="@+id/b2"
android:background="#f4ed79"
android:layout_margin="5dp"
android:textSize="10sp"
android:gravity="center"/>
</LinearLayout>
2、属性attr.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="my_view">
<attr name="b1_text" format="string"/>
<attr name="b1_text_size" format="dimension"/>
<attr name="b1_text_backgroud" format="reference|color"/>
<attr name="b2_text" format="string"/>
<attr name="b2_text_size" format="dimension"/>
<attr name="b2_text_backgroud" format="reference|color"/>
<attr name="t1_text" format="string"/>
<attr name="t1_text_size" format="dimension"/>
<attr name="t1_text_backgroud" format="reference|color"/>
</declare-styleable>
</resources>
3、创建复合控件,进行界面属性的绑定,并设置接口
package com.example.asus.socketapplication.my_view;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.asus.socketapplication.R;
/**
* Created by asus on 2018/11/16.
*/
public class MyView extends LinearLayout {
private Button b1;
private Button b2;
private TextView t1;
private String b1_text;
private float b1_text_size;
private int b1_text_backgroud;
private String b2_text;
private float b2_text_size;
private int b2_text_backgroud;
private String t1_text;
private float t1_text_size;
private int t1_text_backgroud;
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
public void init(Context context,AttributeSet attrs){
LayoutInflater.from(context).inflate(R.layout.my_view,this);
b1=findViewById(R.id.b1);
b2=findViewById(R.id.b2);
t1=findViewById(R.id.t1);
TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.my_view);
b1_text=ta.getString(R.styleable.my_view_b1_text);
b1_text_size=ta.getDimension(R.styleable.my_view_b1_text_size,0);
b1_text_backgroud=ta.getResourceId(R.styleable.my_view_b1_text_backgroud,R.drawable.ic_launcher_foreground);
b2_text=ta.getString(R.styleable.my_view_b2_text);
b2_text_size=ta.getDimension(R.styleable.my_view_b2_text_size,0);
b2_text_backgroud=ta.getResourceId(R.styleable.my_view_b2_text_backgroud,R.drawable.ic_launcher_foreground);
t1_text=ta.getString(R.styleable.my_view_t1_text);
t1_text_size=ta.getDimension(R.styleable.my_view_t1_text_size,0);
t1_text_backgroud=ta.getResourceId(R.styleable.my_view_t1_text_backgroud,R.drawable.ic_launcher_foreground);
t1.setText(t1_text);
t1.setTextSize(t1_text_size);
t1.setBackgroundResource(t1_text_backgroud);
b1.setText(b1_text);
b1.setTextSize(b1_text_size);
b1.setBackgroundResource(b1_text_backgroud);
b2.setText(b2_text);
b2.setTextSize(b2_text_size);
b2.setBackgroundResource(b2_text_backgroud);
b1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
listenr.clickleft();
}
});
b2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
listenr.clickright();
}
});
}
public String getB1_text() {
return b1_text;
}
public void setB1_text(String b1_text) {
this.b1_text = b1_text;
this.invalidate();
}
public float getB1_text_size() {
return b1_text_size;
}
public void setB1_text_size(float b1_text_size) {
this.b1_text_size = b1_text_size;
this.invalidate();
}
public int getB1_text_backgroud() {
return b1_text_backgroud;
}
public void setB1_text_backgroud(int b1_text_backgroud) {
this.b1_text_backgroud = b1_text_backgroud;
this.invalidate();
}
public String getB2_text() {
return b2_text;
}
public void setB2_text(String b2_text) {
this.b2_text = b2_text;
this.invalidate();
}
public float getB2_text_size() {
return b2_text_size;
}
public void setB2_text_size(float b2_text_size) {
this.b2_text_size = b2_text_size;
this.invalidate();
}
public int getB2_text_backgroud() {
return b2_text_backgroud;
}
public void setB2_text_backgroud(int b2_text_backgroud) {
this.b2_text_backgroud = b2_text_backgroud;
this.invalidate();
}
public String getT1_text() {
return t1_text;
}
public void setT1_text(String t1_text) {
this.t1_text = t1_text;
this.invalidate();
}
public float getT1_text_size() {
return t1_text_size;
}
public void setT1_text_size(float t1_text_size) {
this.t1_text_size = t1_text_size;
this.invalidate();
}
public int getT1_text_backgroud() {
return t1_text_backgroud;
}
public void setT1_text_backgroud(int t1_text_backgroud) {
this.t1_text_backgroud = t1_text_backgroud;
this.invalidate();
}
interface Listenr{
public void clickleft();
public void clickright();
}
Listenr listenr;
public void setListenr(Listenr listenr) {
this.listenr = listenr;
}
}
4、再其他地方使用复合控件
<com.example.asus.socketapplication.my_view.MyView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:b1_text="left"
app:b1_text_size="10dp"
app:b1_text_backgroud="#f7ee57"
app:b2_text="right"
app:b2_text_size="10dp"
app:b2_text_backgroud="#f7ee57"
app:t1_text="center"
app:t1_text_size="10dp"
app:t1_text_backgroud="#57eaf7"/>
(三)实现全新自定义控件
1、全新view
1.1view有三种测试模式:
1.2、EXACTLY精确模式,view的长宽都是精确的数字,或者充满父布局
1.3、TO-MOST最大模式,该模式下view的长宽都不能超过父布局的长宽就可以
1.4、UNSPECIFIED未指定,view可以任意大下
2、如果view的长宽是自适应,就需要重写
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
3、如果自定义viewgroup:
重写
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
重写:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
(四)事件传递,及事件拦截
(1)public boolean dispatchTouchEvent(MotionEvent ev)这个方法用来分发TouchEvent
(2)public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent
(3)public boolean onTouchEvent(MotionEvent ev)这个方法用来处理TouchEvent
事件传递viewgroup->view->viewgroup
viewgoup拦截外部解决:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 保存当前touch的纵坐标值
touch = (int) ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 滑动距离大于slop值时,返回true,父容器拦截
if (Math.abs((int) ev.getRawY() - touch) > slop|| mGestureDetector.onTouchEvent(ev))
return true;
break;
}
return super.onInterceptTouchEvent(ev) ;
}
子view消费内部解决:
/**
32 * 使用 outter.requestDisallowInterceptTouchEvent();
33 * 来决定父控件是否对事件进行拦截
34 * @param ev
35 * @return
36 */
37 @Override
38 public boolean dispatchTouchEvent(MotionEvent ev) {
39 int x = (int) ev.getX();
40 int y = (int) ev.getY();
41 switch (ev.getAction()) {
42 case MotionEvent.ACTION_DOWN:
43 mHorizontalEx2.requestDisallowInterceptTouchEvent(true);
44 break;
45 case MotionEvent.ACTION_MOVE:
46 final int deltaX = x-lastYIntercepted;
47 final int deltaY = y-lastYIntercepted;
48 if(Math.abs(deltaX)>Math.abs(deltaY)){
49 mHorizontalEx2.requestDisallowInterceptTouchEvent(false);
50 }
51 break;
52 case MotionEvent.ACTION_UP:
53 break;
54 }
55 lastXIntercepted = x;
56 lastYIntercepted = y;
57 return super.dispatchTouchEvent(ev);
58 }
59 }