本篇博客是介绍Drawable的第十篇博客,跟以往的几篇不同的是,本篇博客一次介绍两个Drawable,之所以这么做,是因为这两个Drawable在xml中使用的标签都是shape。有那么一点点共通的地方。(形状都是通过根标签shape来指定的)
我们还是先看一下shapeDrawable吧!
还是老规矩,先看一下shapedrawable的效果图吧!
主布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
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.shapeandgradientdrawable.GradientActivity">
<TextView
android:id="@+id/tv"
android:gravity="center"
android:background="@drawable/shape_drawable"
android:layout_centerInParent="true"
android:layout_width="200dp"
android:layout_height="100dp"
android:text="Hello World!" />
<Button
android:visibility="gone"
android:layout_below="@id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/start_animation"
android:onClick="startDrawableAnimation"/>
</RelativeLayout>
shape_drawable文件:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"
android:shape="rectangle"
>
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/colorAccent" />
<corners android:radius="5dp" />
<stroke
android:width="2dp"
android:color="@color/colorPrimaryDark"
android:dashGap="3dp"
android:dashWidth="3dp" />
</shape>
java文件:
package com.example.shapeandgradientdrawable;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
import android.os.Bundle;
import android.widget.TextView;
/**
* Created by Administrator on 2017/3/18 0018.
*/
public class ShapeActivity extends Activity {
private TextView tv;
ShapeDrawable drawable;
private int index = 0;
private boolean isTimerRunning = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.shape_layout);
tv = (TextView)findViewById(R.id.tv);
}
}
这里有一个很有意思的现象,通过tv获取背景的drawable是一个gradientdrawable对象而不是一个shapedrawable对象,我不知道为什么会出现这种情况,源码中也没有看出什么东西!!而且在shape的跟标签中有一个useLevel的属性,而在shapeDrawable中根本就没有代码解析这个useLevel这个属性,相反GradientDrawable对这个属性进行的解析,具体的等到了说GradientDrawable的时候在详细说明。
在shape的跟标签下可以使用size 、padding、solid、corners、stroke和gradient几个子标签,其中size用来指定这个drawable的大小的,如果这个drawable是用来当做背景的,那么宽和高设置为1即可,它会和layout中的UI宽高进行对比,取其中的最大值,如果是作为ImageView中src属性的值,那么显示出来的宽高将与size中指定的宽高一致,有兴趣的可以自己试一下。
solid标签中只有一个属性,那就是设置形状内部的填充颜色。
corners这个标签不是在任何情况下都会起作用的,它收到根标签shape中形状属性值的影响,目前我测试的是只有在shape的为rectangle的时候才起作用。
stroke标签中的属性用来设置边界的宽度,颜色,是否是虚线等。
padding的作用与View中padding中作用一样,这里就不做太多的介绍了。
至于Gradient我们还是在GradientDrawable中说吧!!!
现在开始介绍GradientDrawable,主要就是介绍gradient标签。
还是先看一下效果吧!
静态:
在来一个gif图:
主布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
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.shapeandgradientdrawable.GradientActivity">
<TextView
android:id="@+id/tv"
android:gravity="center"
android:background="@drawable/gradient_drawable"
android:layout_centerInParent="true"
android:layout_width="200dp"
android:layout_height="200dp"
android:text="Hello World!" />
<Button
android:layout_below="@id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/start_animation"
android:onClick="startDrawableAnimation"/>
</RelativeLayout>
drawable文件:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"
android:innerRadius="5dp"
android:shape="rectangle"
android:useLevel="true">
<size android:height="1dp"
android:width="1dp"
/>
<solid android:color="@color/colorAccent"/>
<corners android:radius="5dp"/>
<stroke android:color="@color/colorPrimaryDark"
android:dashGap="3dp"
android:dashWidth="3dp"
android:width="2dp"/>
<gradient android:angle="45"
android:centerColor="@color/colorAccent"
android:useLevel="true"
android:type="linear"/>
</shape>
Java文件:
package com.example.shapeandgradientdrawable;
import android.graphics.drawable.GradientDrawable;
import android.os.CountDownTimer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class GradientActivity extends AppCompatActivity {
private TextView tv;
GradientDrawable drawable;
private int index = 0;
GradientDrawable.Orientation[] orientations;
private boolean isTimerRunning = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gradient_layout);
tv = (TextView)findViewById(R.id.tv);
drawable = (GradientDrawable) tv.getBackground();
drawable.setLevel(10000);
orientations = GradientDrawable.Orientation.values();
Log.i("zyq","orientation = "+orientations.length);
}
public void startDrawableAnimation(View view){
if(isTimerRunning){
timer.cancel();
timer.onFinish();
}else{
if (drawable instanceof GradientDrawable){
timer.start();
}
}
}
private CountDownTimer timer = new CountDownTimer(Integer.MAX_VALUE,20) {
@Override
public void onTick(long millisUntilFinished) {
isTimerRunning = true;
if (index<8){
drawable.setOrientation(orientations[index]);
index ++;
}else{
drawable.setOrientation(orientations[0]);
index = 1;
}
}
@Override
public void onFinish() {
isTimerRunning = false;
}
};
}
在gradient标签中有一个uselevel的属性,如果这个属性值指定为true,那么drawable的level值就会影响到drawable中发生变化的起始位置,通过上面的静态图对比就可以发现其中的不同了!
属性angle的值必须是45的倍数,不然会抛出xml解析异常,具体的可以通过查看源码来分析:
if (st.mGradient == LINEAR_GRADIENT) {
int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
angle %= 360;
if (angle % 45 != 0) {
throw new XmlPullParserException(a.getPositionDescription()
+ "<gradient> tag requires 'angle' attribute to "
+ "be a multiple of 45");
}
st.mAngle = angle;
switch (angle) {
case 0:
st.mOrientation = Orientation.LEFT_RIGHT;
break;
case 45:
st.mOrientation = Orientation.BL_TR;
break;
case 90:
st.mOrientation = Orientation.BOTTOM_TOP;
break;
case 135:
st.mOrientation = Orientation.BR_TL;
break;
case 180:
st.mOrientation = Orientation.RIGHT_LEFT;
break;
case 225:
st.mOrientation = Orientation.TR_BL;
break;
case 270:
st.mOrientation = Orientation.TOP_BOTTOM;
break;
case 315:
st.mOrientation = Orientation.TL_BR;
break;
}
}
从这段代码就可以看出,不是任何时候都会对angle进行解析的,只有当梯度变化为线性梯度时,才会对angle进行解析。
if (st.mGradient == LINEAR_GRADIENT) {
final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
switch (st.mOrientation) {
case TOP_BOTTOM:
x0 = r.left; y0 = r.top;
x1 = x0; y1 = level * r.bottom;
break;
case TR_BL:
x0 = r.right; y0 = r.top;
x1 = level * r.left; y1 = level * r.bottom;
break;
case RIGHT_LEFT:
x0 = r.right; y0 = r.top;
x1 = level * r.left; y1 = y0;
break;
case BR_TL:
x0 = r.right; y0 = r.bottom;
x1 = level * r.left; y1 = level * r.top;
break;
case BOTTOM_TOP:
x0 = r.left; y0 = r.bottom;
x1 = x0; y1 = level * r.top;
break;
case BL_TR:
x0 = r.left; y0 = r.bottom;
x1 = level * r.right; y1 = level * r.top;
break;
case LEFT_RIGHT:
x0 = r.left; y0 = r.top;
x1 = level * r.right; y1 = y0;
break;
default:/* TL_BR */
x0 = r.left; y0 = r.top;
x1 = level * r.right; y1 = level * r.bottom;
break;
}
mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
gradientColors, st.mPositions, Shader.TileMode.CLAMP));
} else if (st.mGradient == RADIAL_GRADIENT) {
x0 = r.left + (r.right - r.left) * st.mCenterX;
y0 = r.top + (r.bottom - r.top) * st.mCenterY;
float radius = st.mGradientRadius;
if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
// Fall back to parent width or height if intrinsic
// size is not specified.
final float width = st.mWidth >= 0 ? st.mWidth : r.width();
final float height = st.mHeight >= 0 ? st.mHeight : r.height();
radius *= Math.min(width, height);
} else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
radius *= Math.min(r.width(), r.height());
}
if (st.mUseLevel) {
radius *= getLevel() / 10000.0f;
}
mGradientRadius = radius;
if (radius <= 0) {
// We can't have a shader with non-positive radius, so
// let's have a very, very small radius.
radius = 0.001f;
}
mFillPaint.setShader(new RadialGradient(
x0, y0, radius, gradientColors, null, Shader.TileMode.CLAMP));
} else if (st.mGradient == SWEEP_GRADIENT) {
x0 = r.left + (r.right - r.left) * st.mCenterX;
y0 = r.top + (r.bottom - r.top) * st.mCenterY;
int[] tempColors = gradientColors;
float[] tempPositions = null;
if (st.mUseLevel) {
tempColors = st.mTempColors;
final int length = gradientColors.length;
if (tempColors == null || tempColors.length != length + 1) {
tempColors = st.mTempColors = new int[length + 1];
}
System.arraycopy(gradientColors, 0, tempColors, 0, length);
tempColors[length] = gradientColors[length - 1];
tempPositions = st.mTempPositions;
final float fraction = 1.0f / (length - 1);
if (tempPositions == null || tempPositions.length != length + 1) {
tempPositions = st.mTempPositions = new float[length + 1];
}
final float level = getLevel() / 10000.0f;
for (int i = 0; i < length; i++) {
tempPositions[i] = i * fraction * level;
}
tempPositions[length] = 1.0f;
}
mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
}
从上面这段代码中可以看出当useLevel为true的时候,level值是如何影响到不同的gradient的。
好了,关于GradientDrawable和ShapeDrawable就说这么多,如果还是不太清楚可以给我留言(应该不会有人留言,毕竟太简单了!!!)
这是我的微信公众号,如果可以的话,希望您可以帮忙关注一下,这将是对我最大的鼓励了,谢谢!!