Simple Use of Shape Drawable and Gradient Drawable

This blog is the tenth to introduce Drawable. Unlike previous blogs, this blog introduces two Drawables at a time. The reason for this is that both Drawables use shape tags in xml. There's a little bit in common. (Shapes are specified by the root label shape)

Let's take a look at shape Drawable first.
Still the old rule, first look at the shape drawable effect chart!

Main layout file:

<?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 file:

<?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 file:

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);
    }

}

There is an interesting phenomenon here. Draable to get background through tv is a gradient drawable object rather than a shape drawable object. I don't know why this happens and I don't see anything in the source code!! Moreover, there is a useLevel attribute in the shape tag and there is no code to parse the useLevel attribute in the shape Drawable. On the contrary, Gradient Drawable parses this attribute in detail when it comes to Gradient Drawable.

The size, padding, solid, corners, strong and gradient sub-tags can be used under the shape and tag, where size is used to specify the size of the drawable. If the drawable is used as background, then the width and height can be set to 1. It will be compared with the UI width and height in the layout, taking the most of them. Large value, if it is the value of the src attribute in ImageView, the width shown will be the same as the width specified in size. If you are interested, you can try it on your own.

There is only one attribute in the solid tag, which is to set the fill color inside the shape.

The corners tag does not work under any circumstances. It receives the influence of the shape attribute value in the root tag shape. Currently, what I'm testing is that it only works when the shape is rectangle.

The attributes in the stroke tag are used to set the width, color, dashed line, etc. of the boundary.

The role of padding is the same as that of padding in View, so there's not much to talk about here.

As for Gradient, let's say it in Gradient Drawable!!!

Now let's start with Gradient Drawable, which is mainly about gradient tags.

Let's see the effect first.

Static:




Here's a gif diagram:

Main layout file:

<?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>

Draable file:

<?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 files:

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;
        }
    };
}

There is a uselevel attribute in the gradient tag. If the value of this attribute is specified as true, the level value of drawable will affect the starting position of the change in drawable. The difference can be found by comparing the static graphs above.

The value of the attribute angle must be a multiple of 45, otherwise an xml parsing exception will be thrown. Specifically, it can be analyzed by looking at the source code:

        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;
            }
        } 

From this code, we can see that angle is not always parsed, only when the gradient changes to a linear gradient, it will be parsed.

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));
                }

From the above code, you can see how level values affect different gradient s when useLevel is true.

Well, there's so much to say about Gradient Drawable and Shape Drawable. If it's not clear, you can leave a message for me. (No one should leave a message, after all, it's too simple!!)

This is my Wechat Public Number. If you can, I hope you can help me pay attention to it. This will be my greatest encouragement. Thank you!

Code address:
Material Design contains more complex code, you can extract it by yourself!!!

Keywords: Android Attribute xml encoding

Added by Fly on Thu, 18 Jul 2019 04:36:19 +0300