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="
<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></pre>
效果圖如下:
上面的例子中我們在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();
}
}
}</pre>
效果如下圖:
可以看到,代碼設置和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();
}
}
}</pre> <p>效果如下:</p>
上面的例子使用了三種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)); //起始方向
}
}
}</pre> <p>ScrimUtil代碼如下:</p>
public class ScrimUtil {
private ScrimUtil() {
}
/**
* Creates an approximated cubic gradient using a multi-stop linear gradient. See
* <a >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));
}
}</pre>
最終效果:
使用屬性動畫實現動態漸變
使用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();
}
}</pre>
布局如下:
<RelativeLayout
xmlns:android="
<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></pre> <p>在activity中調用:</p>
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() ;
}
}</pre>
效果如下:
</div>
</code></code></code></code></code></code></code></code></code></code></code></code></code>