上一篇Android-自定义View-onDraw方法起步我们已经基本了解了绘制。不过有点不太灵活,大部分小白作者都是这样开场哈!嘿嘿....
比如最简单的就是我们圆圈的半径应该有xml里面直接指定,比如绘制的颜色应该xml配置好,然后我们绘制时获取半径和颜色就可以进行绘制了。这样大大增加了灵活性,好伐!
So,涉及到如何自定义属性以及获取属性。
一、自定义属性
主要涉及到两个 attrs.xml 、 styles.xml。网上有很多关于属性主题的的详细说明。我们只简单关心两点:
1. attrs.xml - 用来定义各种自定义属性,比如显示的文本,绘制的颜色,绘制的半径,这些文本格式就是字符串(string),半径就是整数(integer), 绘制的颜色就是字符("#ffffff"),然后等等属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--声明我们的属性,名称为radius,取值类型为尺寸类型(dp,px等)-->
<attr name="radius" format="dimension"></attr>
</resources>
注意: format蛮重要的,关于具体格式后面找到官方的链接了我们专门总结下,暂时知道几个就行
2. styles.xml - 用来给attrs.xml定义的自定义属性进行赋值 - MyTextView01
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="MyTextView01">
<item name="radius">50</item>
</style>
</resources>
二、获取属性
1. xml里面设置属性或者style
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".MainActivity">
<me.heyclock.hl.customcopy.MyTextView01
android:layout_width="200dp"
android:layout_height="200dp"
app:radius="50dp"
style="@style/MyTextView01"
android:background="@color/colorPrimary" />
</android.support.constraint.ConstraintLayout>
2.根据AttributeSet类的官方文档,可以获取到属性个数,进而可以进行遍历获取相关属性和值
public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
///< 获取自定义属性值
for (int i = 0; i < attrs.getAttributeCount(); ++i){
Log.e("attrs", "" + i + "-name=" + attrs.getAttributeName(i) + " value=" + attrs.getAttributeValue(i));
}
///< 1\. 做一些绘制初始化
canvas = new Canvas(); ///< 也可以指定绘制到Bitmap上面 -> Canvas(Bitmap bitmap)
paint = new Paint();
paint.setColor(Color.parseColor("#F50808"));
}
看结果:
image2. 1 由于我们直接调用的是
image而我们的布局文件里面是 - 有资源ID的引用
image2. 2 所以上面只能拿到资源Id,我们还需要根据Id进一步获取对应的值:
int colorId = attrs.getAttributeResourceValue(0, -1);
Log.e("attrs", "colorId= " + colorId);
Log.e("attrs", "color= " + Integer.toHexString(getResources().getColor(colorId)));
看看是不是就可以获取了呀呀....真的是帅的一笔....
image2. 3 这个时候是不是还有别的方法了 - **TypedArray **?我们经常看别人自定义的控件里面获取属性基本都是酱紫写的:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);
String text = ta.getString(R.styleable.test_testAttr);
int textAttr = ta.getInteger(R.styleable.test_text, -1);
ta.recycle();
就是这个TypedArray,这个时候我们需要定义一个styleable
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView01">
<attr name="radius" format="dimension"></attr>
</declare-styleable>
</resources>
布局一下:activity_main.xml
<me.heyclock.hl.customcopy.MyTextView01
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
app:radius="50dp" />
然后我们就可以利用TypedArray获取属性值了
///< TypedArray的方式
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView01);
///< getDimension() getDimensionPixelOffset() getDimensionPixelSize()
/// --这三个方法都是根据DisplayMetrics获取相应的值,不同在于方法1直接保存float型数据,方法2直接对float取整,方法3对float小数先四舍五入后取整。
radius = ta.getDimensionPixelOffset(R.styleable.MyTextView01_radius, 50);
Log.e("attrs", "像素值 radius= " + radius);
ta.recycle();
image
2. 4 然后之前的styles呢?? 我们可以把属性值赋值在styles.xml里面定义:
styles.xml
<style name="MyTextView01">
<item name="radius">150dp</item>
</style>
然后布局里面使用:
<me.heyclock.hl.customcopy.MyTextView01
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
style="@style/MyTextView01" />
--那右怎么获取style的属性了?也是一样的获取呀。。style只是给其赋值而已....
imageNice...至少又加深了对属性、style的认识。
2. 4. 1 我们属性是不是也可以app:radius="50dp"这样赋值 ?
<me.heyclock.hl.customcopy.MyTextView01
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
style="@style/MyTextView01"
app:radius="50dp" />
疑问:这个时候就会有疑问呢?到底是styles.xml里面赋值管用还是app:radius里面管用?
根据结果发现app:radius管用。。。
image关于这个有个优先级: AttributeSet(layout) > defStyleAttr(@AttrRes主题可配置样式)> defStyleRes(@StyleRes默认样式)> NULL(主题中直接指定)
Style我们暂时不用这种方式哈!先用app的方式进行属性赋值,将之前的demo扩展一下。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
tools:context=".MainActivity">
<me.heyclock.hl.customcopy.MyTextView01
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
style="@style/MyTextView01"
app:radius="50dp" />
</android.support.constraint.ConstraintLayout>
另外attrs.xml里面我们新增宽高的属性名称,方便我们获取宽高设置
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView01">
<attr name="radius" format="dimension"></attr>
<attr name="ccolor" format="string"></attr>
<attr name="android:layout_width"/>
<attr name="android:layout_height"/>
</declare-styleable>
</resources>
另外我们内部的宽高计算,中心点的计算,以及红色区域的范围,我们均采用动态计算,不再写死:
int minX = (width - radius * 2)/2;
int maxX = width/2 + radius;
int minY = (height - radius * 2)/2;
int maxY = height/2 + radius;
canvas.drawCircle(width/2, height/2,
radius + changeRadius, paint);
最后完整的文件 MyTextView01.java
package me.heyclock.hl.customcopy;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import java.util.Timer;
import java.util.TimerTask;
/*
*@Description: 自定义绘制文本
*@Author: hl
*@Time: 2018/10/12 9:37
*/
public class MyTextView01 extends View {
/* 官方文档:
https://developer.android.google.cn/reference/android/graphics/Canvas
https://developer.android.google.cn/reference/android/graphics/Paint
*/
private Context context;///< 上下文
private Canvas canvas; ///< 画布
private Paint paint; ///< 画笔
///< 做红色点击区域限制
private boolean bIsDownInRedRegion = false;
///< 定时刷新
private Timer timer = null;
///< 圆圈半径
private int radius;
///< 圆圈颜色
private String color;
///< 控件宽度和高度
private int width;
private int height;
/**
* 刷新绘制+增量变化
*/
private static final int STEP_RADIUS = 10; ///< 每次半径增加10
private int changeRadius = 0; ///< 变化量记录,达到50时则开始减;达到0就开始增加
private boolean addFlag = true; ///< 标记是否增加增量
public MyTextView01(Context context) {
this(context, null);
}
public MyTextView01(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, 0, 0);
}
public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.context = context;
///< 获取自定义属性值
for (int i = 0; i < attrs.getAttributeCount(); ++i){
Log.e("attrs", "" + i + "-name="
+ attrs.getAttributeName(i)
+ " value=" + attrs.getAttributeValue(i));
}
int colorId = attrs.getAttributeResourceValue(0, -1);
Log.e("attrs", "colorId= " + colorId);
Log.e("attrs", "color= " + Integer.toHexString(getResources().getColor(colorId)));
///< TypedArray的方式
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView01);
///< getDimension() getDimensionPixelOffset() getDimensionPixelSize()
/// --这三个方法都是根据DisplayMetrics获取相应的值,不同在于方法1直接保存float型数据,方法2直接对float取整,方法3对float小数先四舍五入后取整。
radius = ta.getDimensionPixelOffset(R.styleable.MyTextView01_radius, 50);
color = ta.getString(R.styleable.MyTextView01_ccolor);
width = ta.getDimensionPixelOffset(R.styleable.MyTextView01_android_layout_width, 200);
height = ta.getDimensionPixelOffset(R.styleable.MyTextView01_android_layout_height, 200);
///< 做一个兼容,如果半径超过了控件宽或者高
int minWH = width;
if (width > height){
minWH = height;
}
if ((radius * 2) > minWH){
radius = minWH / 2;
}
Log.e("attrs", "像素值 radius= " + radius);
Log.e("attrs", "color= " + color);
Log.e("attrs", "width= " + width);
Log.e("attrs", "height= " + height);
ta.recycle();
///< 1\. 做一些绘制初始化
canvas = new Canvas(); ///< 也可以指定绘制到Bitmap上面 -> Canvas(Bitmap bitmap)
paint = new Paint();
paint.setColor(Color.parseColor(color));
}
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
///< 2.进行绘制
///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
canvas.drawCircle(width/2, height/2,
radius + changeRadius, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("test", "getX=" + event.getX());
Log.e("test", "getY=" + event.getY());
//Log.e("test", "getRawX=" + event.getRawX());
//Log.e("test", "getRawY=" + event.getRawY());
int x = (int) event.getX();
int y = (int) event.getY();
///< 控件大小是: dp2px(context, 200) * dp2px(context, 200)
///< 圆心坐标是: dp2px(context, 100) * dp2px(context, 100)
///< 圆半径是: dp2px(context, 50)
///< 所以点击区域就是左上角范围(dp2px(context, 50), dp2px(context, 50))
///< 右下角范围:(dp2px(context, 150), dp2px(context, 150))
int minX = (width - radius * 2)/2;
int maxX = width/2 + radius;
int minY = (height - radius * 2)/2;
int maxY = height/2 + radius;
Log.e("test", "x=" + x);
Log.e("test", "y="+ y);
Log.e("test", "minX=" + minX);
Log.e("test", "maxX="+ maxX);
Log.e("test", "minY=" + minY);
Log.e("test", "maxY="+ maxY);
Log.e("test", "event.getAction()=" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (x >= minX && x <= maxX &&
y >= minY && y <= maxY){
bIsDownInRedRegion = true;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (bIsDownInRedRegion){
bIsDownInRedRegion = false;
if (x >= minX && x <= maxX &&
y >= minY && y <= maxY){
///< 抬手时我们就可以启动定时器进行绘制刷新了
Log.e("test", "红色区域点击了呀,sb");
if (null == timer){
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
///< Handler也行
((Activity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
updateDraw();
}
});
}
}, 0, 100);
}else{
timer.cancel();
timer = null;
}
}
}
break;
}
return true;
}
/**
* 刷新绘制+增量变化
*/
private void updateDraw(){
changeRadius = addFlag ? (changeRadius += STEP_RADIUS) : (changeRadius -= STEP_RADIUS);
if (changeRadius > 50){
addFlag = false;
}else if (changeRadius < 0){
addFlag = true;
}
invalidate();
}
/**
* dp转px
* @param dp
* @return
*/
public static int dp2px(Context context, int dp){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}
}
其中半径做了一个兼容,怕超过屏幕宽高
///< 做一个兼容,如果半径超过了控件宽或者高
int minWH = width;
if (width > height){
minWH = height;
}
if ((radius * 2) > minWH){
radius = minWH / 2;
}
Last
1\. attrs.xml里面的declare-styleable以及item,android会根据其在R.java中生成一些常量方便我们使用(aapt干的),本质上,我们可以不声明declare-styleable仅仅声明所需的属性即可。
2\. 我们在View的构造方法中,可以通过AttributeSet去获得自定义属性的值,但是比较麻烦,而TypedArray可以很方便的便于我们去获取。
3\. 我们在自定义View的时候,可以使用系统已经定义的属性。
自定义属性基本到这。后面遇到什么再深入和总结哈....喵喵!
网友评论