Android渐变研究

下面介绍一个android实现渐变的方式

GradientDrawable

用GradientDrawable实现渐变可以通过xml或者代码实现,xml实现需要在drawable下建立xml文件,在 标签下建立 标签。

例如gradlient_background.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <gradient android:startColor="#aa000000"
                      android:endColor="@android:color/transparent"
                      android:angle="90"
                />
        </shape>
    </item>
</selector>

设置方法如下:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        android:id="@+id/layout_bottom"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_alignParentBottom="true"
        android:background="@drawable/gradlient_background"
        android:orientation="horizontal"
        />

</RelativeLayout>

效果图如下:

上面的例子中我们在gradient标签中设置了startColor,endColor,angle用来表示开始结束的颜色和变化方向。 gradient标签的所有属性说明如下:

android:angle:(Integer) 渐变的角度,线性渐变时才有效,必须是45的倍数,0表示从左到右,90表示从下到上

android:centerX:(Float)渐变中心的相对X坐标,放射渐变时才有效,在0.0到1.0之间,默认为0.5,表示在正中间

android:centerY:(Float)渐变中心的相对X坐标,放射渐变时才有效,在0.0到1.0之间,默认为0.5,表示在正中间

android:centerColor :(Color)中间点的色值

android:endColor : (Color)结束的色值

android:startColor:(Color)开始的色值。

android:gradientRadius:(Float)渐变的半径,只有在android:type="radial"的时候有效。

android:type :有三种类型 "linear" 线性渐变, "radial" 放射渐变,设置该项时,android:gradientRadius也必须设置 "sweep" 扫描性渐变

android:useLevel : 如果为true,将被当成LevelListDrawable使用。

除了用xml设置,还可以在编码中设置, 标签对应的类是GradientDrawable,GradientDrawable是Drawable的子类。 代码如下:

public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new SampleView(this));
    }

    private static class SampleView extends View {
        private Rect mRect;
        private GradientDrawable mDrawable;

        public SampleView(Context context) {
            super(context);
            setFocusable(true);

            mRect = new Rect(0, 0, 300, 300);
            mDrawable = new GradientDrawable(GradientDrawable.Orientation.TL_BR,
                    new int[] { 0xaa000000,
                            0xFFFFFFFF });
            mDrawable.setShape(GradientDrawable.RECTANGLE);
            mDrawable.setGradientRadius((float)(Math.sqrt(2) * 60));
        }

        static void setCornerRadii(GradientDrawable drawable, float r0,
                                   float r1, float r2, float r3) {
            drawable.setCornerRadii(new float[] { r0, r0, r1, r1,
                    r2, r2, r3, r3 });
        }

        @Override protected void onDraw(Canvas canvas) {

            mDrawable.setBounds(mRect);

            float r = 16;

            canvas.save();
            canvas.translate(10, 10);
            mDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
            setCornerRadii(mDrawable, r, r, 0, 0);
            mDrawable.draw(canvas);
            canvas.restore();

            canvas.save();
            canvas.translate(10 + mRect.width() + 10, 10);
            mDrawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
            setCornerRadii(mDrawable, 0, 0, r, r);
            mDrawable.draw(canvas);
            canvas.restore();

            canvas.translate(0, mRect.height() + 10);

            canvas.save();
            canvas.translate(10, 10);
            mDrawable.setGradientType(GradientDrawable.SWEEP_GRADIENT);
            setCornerRadii(mDrawable, 0, r, r, 0);
            mDrawable.draw(canvas);
            canvas.restore();

        }
    }
}

效果如下图:

可以看到,代码设置和xml设置大同小异,注意实例化的操作:

public GradientDrawable(Orientation orientation, int[] colors) {
        this(new GradientState(orientation, colors));
    }

第一个参数是一个枚举,表示渐变方向,这个用来相当于xml里面的angle,只不过angle是用45的倍数表示方向,而枚举看上去更清楚了。

public enum Orientation {
        /** draw the gradient from the top to the bottom */
        TOP_BOTTOM,
        /** draw the gradient from the top-right to the bottom-left */
        TR_BL,
        /** draw the gradient from the right to the left */
        RIGHT_LEFT,
        /** draw the gradient from the bottom-right to the top-left */
        BR_TL,
        /** draw the gradient from the bottom to the top */
        BOTTOM_TOP,
        /** draw the gradient from the bottom-left to the top-right */
        BL_TR,
        /** draw the gradient from the left to the right */
        LEFT_RIGHT,
        /** draw the gradient from the top-left to the bottom-right */
        TL_BR,
    }

第二个参数是一个color数组,相当于startColor,endColor,centerColor,其中centerColor可以省略,但是至少要设置两个颜色。 mDrawable.setGradientType可以设置三种type同xml一样,分别是GradientDrawable.LINEAR_GRADIENT,GradientDrawable .RADIAL_GRADIENT,GradientDrawable.SWEEP_GRADIENT。

Shader类的子类

Shader类的子类创建允许使用多种固体颜色填充绘图对象的Paint,功能不只是实现渐变填充。有三Shader是用来做渐变的: LinearGradient、RadialGradient和 SweepGradient. 看名字就知道这三种和上面的GradientDrawable的三种type是对应的。 只不过是用Shader实现了。

要在绘图的时候使用一个Shader,可以使用setShader方法将其应用到一个Paint中,如下面的代码所示:

Paint shaderPaint = new Paint();
    shaderPaint.setShader(myLinearGradient);

使用这个Paint所绘制的任何东西都将使用你指定的Shader进行填充,而不是使用Paint本身的颜色进行填充。下面使用LinearGradient实现渐变,对于RadialGradient和 SweepGradient使用很类似。

public class TestActivity extends Activity {

            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(new SampleView(this));
            }

            private static class SampleView extends View {
                private Paint mPaint;
                private Rect mRect;
                LinearGradient lg1 ;
                LinearGradient lg2 ;
                LinearGradient lg3 ;

                public SampleView(Context context) {
                    super(context);
                    setFocusable(true);

                    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                    mRect = new Rect(0, 0, 300, 300);

                     lg1 = new LinearGradient(0,0,150,150, Color.TRANSPARENT,Color.BLACK,
                            Shader.TileMode.MIRROR);
                     lg2 = new LinearGradient(0,0,150,150, Color.TRANSPARENT,Color.BLACK,
                            Shader.TileMode.CLAMP);
                     lg3 = new LinearGradient(0,0,150,150, Color.TRANSPARENT,Color.BLACK,
                            Shader.TileMode.REPEAT);

                }

                @Override protected void onDraw(Canvas canvas) {
                    canvas.save();
                    canvas.translate(10, 10);
                    mPaint.setShader(lg1) ;
                    canvas.drawRect(mRect,mPaint);
                    canvas.restore();

                    canvas.save();
                    canvas.translate(10 + mRect.width() + 10, 10);
                    mPaint.setShader(lg2) ;
                    canvas.drawRect(mRect,mPaint);
                    canvas.restore();

                    canvas.save();
                    canvas.translate(10,10 + mRect.height() + 10);
                    mPaint.setShader(lg3) ;
                    canvas.drawRect(mRect,mPaint);
                    canvas.restore();
                }
            }
        }

效果如下:

上面的例子使用了三种Shader TileModes,如果Shader画刷所定义的区域比要填充的区域小,那么TileMode将会决定如何处理剩余的区域:

MIRROR 在水平和垂直方向上拉伸Shader图像,这样每一个图像就都能与上一个缝合了。

CLAMP 使用Shader的边界颜色来填充剩余的空间。

REPEAT 在水平和垂直方向上重复Shader图像,但不拉伸它。

LinearGradient有两种方式实例化:

LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)

他们的不同之处为参数中第一种方法可以用颜色数组,和位置来实现更细腻的过渡效果, 比如颜色采样int[] colors数组中存放20种颜色,则渐变将会逐一处理。而第二种方法参数仅为起初颜色color0和最终颜色color1。

自定义渐变

可以利用工具类重新计算LinearGradient的颜色参数,从而实现更柔和的渐变,仍然使用最开始的LinearLayout布局测试:

public class TestActivity extends Activity  {

         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_test);

             if(Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                 View bottom = findViewById(R.id.layout_bottom);
                 bottom.setBackground(
                         ScrimUtil.makeCubicGradientScrimDrawable(
                                 0xaa000000, //颜色
                                 8, //渐变层数
                                 Gravity.BOTTOM)); //起始方向
             }
         }

     }

ScrimUtil代码如下:

public class ScrimUtil {

    private ScrimUtil() {
    }

    /**
     * Creates an approximated cubic gradient using a multi-stop linear gradient. See
     * <a href="https://plus.google.com/+RomanNurik/posts/2QvHVFWrHZf">this post</a> for more
     * details.
     */
    public static Drawable makeCubicGradientScrimDrawable(int baseColor, int numStops, int gravity) {
        numStops = Math.max(numStops, 2);

        PaintDrawable paintDrawable = new PaintDrawable();
        paintDrawable.setShape(new RectShape());

        final int[] stopColors = new int[numStops];

        int red = Color.red(baseColor);
        int green = Color.green(baseColor);
        int blue = Color.blue(baseColor);
        int alpha = Color.alpha(baseColor);

        for (int i = 0; i < numStops; i++) {
            float x = i * 1f / (numStops - 1);
            float opacity = constrain(0, 1, (float) Math.pow(x, 3));
            stopColors[i] = Color.argb((int) (alpha * opacity), red, green, blue);
        }

        final float x0, x1, y0, y1;
        switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.LEFT:  x0 = 1; x1 = 0; break;
            case Gravity.RIGHT: x0 = 0; x1 = 1; break;
            default:            x0 = 0; x1 = 0; break;
        }
        switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.TOP:    y0 = 1; y1 = 0; break;
            case Gravity.BOTTOM: y0 = 0; y1 = 1; break;
            default:             y0 = 0; y1 = 0; break;
        }

        paintDrawable.setShaderFactory(new ShapeDrawable.ShaderFactory() {
            @Override
            public Shader resize(int width, int height) {
                LinearGradient linearGradient = new LinearGradient(
                        width * x0,
                        height * y0,
                        width * x1,
                        height * y1,
                        stopColors, null,
                        Shader.TileMode.CLAMP);
                return linearGradient;
            }
        });

        return paintDrawable;
    }

    private  static float constrain(float min, float max, float v) {
        return Math.max(min, Math.min(max, v));
    }
}

最终效果:

使用属性动画实现动态渐变

使用ArgbEvaluator.evaluate(floatfraction, Object startValue, Object endValue); 可以实现颜色动态渐变,使用一个自定义view来测试:

public class TestLayout extends View {

    public TestLayout(Context context) {
        super(context);
        init(null, 0);
    }
    public TestLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }
    public TestLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    @SuppressWarnings("deprecation")
    private void init(AttributeSet attrs, int defStyle) {
    }

    /**
     * =============================================================================================
     * The Animator methods
     * =============================================================================================
     */
    /**
     * 开始背景动画(此处为属性动画)
     */
    void startBackgroundAnimator(){
        /*
         *参数解释:
         *target:设置属性动画的目标类,此处是当前自定义view所以使用this
         *propertyName:属性名称。(要对View的那个属性执行动画操作)
         *values数组:根据时间的推移动画将根据数组的内容进行改变
         */
        ValueAnimator anim = ObjectAnimator.ofInt(this, "backgroundColor", Color.RED,Color.BLUE,Color.GRAY,Color.GREEN);
        //动画持续时间为3秒
        anim.setDuration(3000);
        /*
         * ArgbEvaluator:这种评估者可以用来执行类型之间的插值整数值代表ARGB颜色。
         * FloatEvaluator:这种评估者可以用来执行浮点值之间的插值。
         * IntEvaluator:这种评估者可以用来执行类型int值之间的插值。
         * RectEvaluator:这种评估者可以用来执行类型之间的插值矩形值。
         *
         * 由于本例是改变View的backgroundColor属性的背景颜色所以此处使用ArgbEvaluator
         */
        anim.setEvaluator(new ArgbEvaluator());
        //设置动画重复次数,此处设置无限重复
        anim.setRepeatCount(ValueAnimator.INFINITE);
        //设置重复模式
        anim.setRepeatMode(ValueAnimator.REVERSE);
        //开启动画
        anim.start();
    }
}

布局如下:

<RelativeLayout
              xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              >

              <com.mxn.soul.demo.TestLayout
                  android:id="@+id/layout_bottom"
                  android:layout_width="match_parent"
                  android:layout_height="300dp"
                  android:layout_alignParentBottom="true"
                  android:orientation="horizontal"
                  />

          </RelativeLayout>

在activity中调用:

public class TestActivity extends Activity  {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        TestLayout bottom = (TestLayout) findViewById(R.id.layout_bottom);
        bottom.startBackgroundAnimator() ;
    }

}

效果如下:

时间: 2024-10-25 12:42:50

Android渐变研究的相关文章

Android多线程研究(4)——从一道面试题说起

有一道这种面试题:开启一个子线程和主线程同一时候运行,子线程输出10次后接着主线程输出100次,如此重复50次.先看以下代码: package com.maso.test; /** * * @author Administrator * 两个线程,当中是一个主线程,第一个线程先运行输出10次,主线程接着运行输出100次,如此重复50次 */ public class ThreadTest3 implements Runnable{ private static Test test; @Overr

Android多线程研究(3)——线程同步和互斥及死锁

为什么会有线程同步的概念呢?为什么要同步?什么是线程同步?先看一段代码: package com.maso.test; public class ThreadTest2 implements Runnable{ private TestObj testObj = new TestObj(); public static void main(String[] args) { ThreadTest2 tt = new ThreadTest2(); Thread t1 = new Thread(tt,

Android多线程研究(9)——线程锁Lock

在前面我们在解决线程同步问题的时候使用了synchronized关键字,今天我们来看看Java 5.0以后提供的线程锁Lock. Lock接口的实现类提供了比使用synchronized关键字更加灵活和广泛的锁定对象操作,而且是以面向对象的方式进行对象加锁. @Override public void run() { while(true){ Lock lock = new ReentrantLock(); try { lock.lock(); Thread.sleep(new Random()

Android多线程研究(1)——线程基础及源代码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override p

Android多线程研究(6)——多线程之间数据隔离

在上一篇<Android多线程研究(5)--线程之间共享数据>中对线程之间的数据共享进行了学习和研究,这一篇我们来看看如何解决多个线程之间的数据隔离问题,什么是数据隔离呢?比如说我们现在开启了两个线程,这两个线程都要同时给同一个全局变量data赋值,各个线程操作它赋值后的变量数据,这里就需要用到隔离.先看一段代码: import java.util.Random; public class ThreadLocalTest { private static int data = 0; publi

Android多线程研究(1)——线程基础及源码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门容易,但是要完成一个完善的产品却不容易,让我们从线程开始一步步深入Android内部. 一.线程基础回顾 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override publi

Android多线程研究(5)——线程之间共享数据

一.如果是每个线程都执行相同的代码,则可以使用同一个Runnable来实现共享 public class MultiThreadShareData { public static void main(String[] args) { new Thread(new ShareData()).start(); new Thread(new ShareData()).start(); } static class ShareData implements Runnable{ private int j

android 渐变drawable

渐变Drawable是使用<gradient>标记作为形状Drawable定义中的子节点定义的. 每个渐变Drawable都要求至少要有一个startColor和endColor属性,并且支持一个可选的middleColor属性.通过使用type属性,可以把渐变定义为以下的某种类型: 线性:这是默认的渐变类型,它显示了按照angle属性定义的角度从startColor到endColor的直接颜色过渡. 辐射:从形状的外边界到中心绘制从startColor到endColor的圆形渐变. 扫描:绘

Android安全研究经验谈

一.安全研究做什么 攻击角度:对某个模块进行漏洞挖掘的方法,对某个漏洞进行利用的技术,通过逆向工程破解程序.解密数据,对系统或应用进行感染.劫持等破坏安全性的攻击技术等. 防御角度:查杀恶意应用的方法,检测和防范漏洞利用,为增强系统和第三方应用的安全性提供解决方案等. 通常,攻与防是相对模糊的,一种安全技术往往两端都适用,也许更值得留意的是一种技术背后的思路和意识. 二.需要具备的知识 安全涉及的知识范畴是无限的,但大多数时候可以遇到问题再去了解相关知识. 三.要掌握的理论知识 操作系统原理,非