Android高级UI开发(四十)shader渲染器介绍
Shader是什么,Canvas可以绘制图形(圆形、弧形、矩形等),Shader是为这些图形着色的,改变这些图形外观的,例如在一个圆形上将图片贴在圆形上,就可以实现圆形头像控件,在这里BitmapShader改变了圆形这个图形的外观,将图片内容附着到了图形上面。Shader不只有BitmapShader,它总共包括如下Shader:BitmapShader、LinearGradient、SweepGradient、RadialGradient、ComposeShader。接下来我们会详细讲解这几个Shader的用法。
一、基础知识
1. BitmapShader
我们前面已经提过,它是用图片来改变图形外观的Shader,这里就以制作圆形头像为例。代码很简单,如下所示:
-
package com.cb.paint_gradient;
-
import android.content.Context;
-
import android.graphics.Bitmap;
-
import android.graphics.BitmapShader;
-
import android.graphics.Canvas;
-
import android.graphics.Color;
-
import android.graphics.ComposeShader;
-
import android.graphics.LinearGradient;
-
import android.graphics.Matrix;
-
import android.graphics.Paint;
-
import android.graphics.RadialGradient;
-
import android.graphics.Rect;
-
import android.graphics.RectF;
-
import android.graphics.Shader;
-
import android.graphics.SweepGradient;
-
import android.graphics.drawable.BitmapDrawable;
-
import android.graphics.drawable.ShapeDrawable;
-
import android.graphics.drawable.shapes.OvalShape;
-
import android.support.annotation.Nullable;
-
import android.util.AttributeSet;
-
import android.view.View;
-
-
/**
-
* Created by xw.gao
-
*/
-
-
public class MyShaderView extends View {
-
private Paint mPaint;
-
private Bitmap mBitMap = null;
-
-
private int mWidth;
-
private int mHeight;
-
//gxw-private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};
-
-
public MyShaderView(Context context, @Nullable AttributeSet attrs) {
-
super(context, attrs);
-
mBitMap = ((BitmapDrawable)getResources().getDrawable(R.drawable.head)).getBitmap();
-
mPaint = new Paint();
-
mWidth = mBitMap.getWidth();
-
mHeight = mBitMap.getHeight();
-
}
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
canvas.drawColor(Color.WHITE);
-
-
/**
-
* TileMode.CLAMP 拉伸最后一个像素去铺满剩下的地方
-
* TileMode.MIRROR 通过镜像翻转铺满剩下的地方。
-
* TileMode.REPEAT 重复图片平铺整个画面(电脑设置壁纸)
-
*/
-
BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.MIRROR,
-
Shader.TileMode.MIRROR);
-
mPaint.setShader(bitMapShader);
-
mPaint.setAntiAlias(true);
-
canvas.drawCircle(500,900, 500 ,mPaint);
-
}
-
}
主要看以下2处代码:
(1) BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.MIRROR,Shader.TileMode.MIRROR);
mPaint.setShader(bitMapShader);
这行代码生成了一个BitmapShader,指明了图片,以及图片将要附着在图形上的方式是:Shader.TileMode.MIRROR(镜像模式)。如果图形的面积大于图片的面积,则图片会重复生成,并且相邻图片成镜像。显示效果见下面(2)中的图片。
(2) canvas.drawCircle(500,900, 500 ,mPaint); 这里画了一个圆形,前2个参数是圆心,第3个参数是半径,最后一个参数paint已经设置了我们之前的bitMapShader。那最终的显示效果将是将图片mBitMap粘贴在圆形上。在这里我们圆形的大小远大于图片的大小,且 覆盖模式是 Shader.TileMode.MIRROR。所以显示效果就如下图所示:
把上述canvas.drawCircle这行代码改为如下代码,可以将图形设置为矩形,然后把图片贴在矩形里。
canvas.drawRect(new Rect(0,0 , 1000, 1600),mPaint);
那么这时的效果图如下:
再把上述canvas.drawRect代码换成如下代码,则是绘制一个椭圆,然后图片贴在椭圆里。
canvas.drawOval(new RectF(0 , 0, mWidth, mHeight),mPaint);
这里的mWidth和mHeight是图片的宽和高。也就是说这个椭圆的外切矩形的宽高就是图片的宽高。这时图片贴上去,一张图片就可以铺满这个椭圆。效果如下图(由于我们这张图片的宽和高比较相近,所以呈现出来的就是一个近乎圆形的头像):
至此,BitmapShader就讲完了,一句话,它是用图片改变图形外观的渲染器。
2. LinearGradient线性渲染
LinearGradient线性渲染就是颜色对图形外观的改变,主要是在着色上面。之所以为线性,指的是在1条直线上的颜色渐变。比如从一个矩形图形的左上角到右下角,颜色可以从绿色渐变为蓝色。我们实现这么一个例子:让一个矩形的颜色从左上角到右下角渐变。代码如下:
-
package com.cb.paint_gradient;
-
import android.content.Context;
-
import android.graphics.Bitmap;
-
import android.graphics.BitmapShader;
-
import android.graphics.Canvas;
-
import android.graphics.Color;
-
import android.graphics.ComposeShader;
-
import android.graphics.LinearGradient;
-
import android.graphics.Matrix;
-
import android.graphics.Paint;
-
import android.graphics.RadialGradient;
-
import android.graphics.Rect;
-
import android.graphics.RectF;
-
import android.graphics.Shader;
-
import android.graphics.SweepGradient;
-
import android.graphics.drawable.BitmapDrawable;
-
import android.graphics.drawable.ShapeDrawable;
-
import android.graphics.drawable.shapes.OvalShape;
-
import android.support.annotation.Nullable;
-
import android.util.AttributeSet;
-
import android.view.View;
-
-
/**
-
* Created by xw.gao
-
*/
-
-
public class MyShaderView extends View {
-
private Paint mPaint;
-
private Bitmap mBitMap = null;
-
-
private int mWidth;
-
private int mHeight;
-
private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};
-
-
public MyShaderView(Context context, @Nullable AttributeSet attrs) {
-
super(context, attrs);
-
mBitMap = ((BitmapDrawable)getResources().getDrawable(R.drawable.head)).getBitmap();
-
mPaint = new Paint();
-
mWidth = mBitMap.getWidth();
-
mHeight = mBitMap.getHeight();
-
}
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
canvas.drawColor(Color.WHITE);
-
/*gxw-s for BitmapShader
-
* TileMode.CLAMP 拉伸最后一个像素去铺满剩下的地方
-
* TileMode.MIRROR 通过镜像翻转铺满剩下的地方。
-
* TileMode.REPEAT 重复图片平铺整个画面(电脑设置壁纸)
-
BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.MIRROR,Shader.TileMode.MIRROR);
-
mPaint.setShader(bitMapShader);
-
mPaint.setAntiAlias(true);
-
//canvas.drawCircle(500,900, 500 ,mPaint);图形为圆形
-
//canvas.drawRect(new Rect(0,0 , 1000, 1600),mPaint);图形为矩形
-
//canvas.drawOval(new RectF(0 , 0, mWidth, mHeight),mPaint);图形为椭圆
-
gxw-e for BitmapShader */
-
-
-
-
/* 线性渐变
-
* x0, y0, 起始点
-
* x1, y1, 结束点
-
* int[] mColors, 中间依次要出现的几个颜色
-
* float[] positions,数组大小跟colors数组一样大,中间依次摆放的几个颜色分别放置在那个位置上(参考比例从左往右)
-
* tile
-
*/
-
LinearGradient linearGradient = new LinearGradient( 0, 0,800, 800, mColors, null, Shader.TileMode.CLAMP);
-
// linearGradient = new LinearGradient(0, 0, 400, 400, mColors, null, Shader.TileMode.REPEAT);
-
mPaint.setShader(linearGradient);
-
canvas.drawRect(0, 0, 800, 800, mPaint);
-
}
-
}
还是看2处代码,
(1) LinearGradient linearGradient = new LinearGradient( 0, 0,800, 800, mColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
在这里生成了一个线性渲染器LinearGradient,并将它设置给paint.
参数解释
参数1:渐变线起点的x坐标
参数2:渐变线起点的y坐标
参数3:渐变线终点的x坐标
参数4:渐变线终点的y坐标
参数5:渐变的颜色,是一个数组,可以有多种颜色间的渐变。如,
private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};从红色先渐变为绿色;再由绿色渐变为蓝色;最后再由蓝色渐变为黄色。
参数6:float [ ]position数组,与参数5中color数组的元素个数是相同的,为null时,各渐变过程在对角线(渐变线)上的跨度是均匀的。如红到绿占1/3,绿到蓝再占1/3,蓝色到黄色最后再占约1/3。总之是平分的。效果图1如下:
图1
position参数不为null时,例如float[]position = {0,0.3f,0.8f,1};
红色渐变到绿色时,这2种颜色的跨度为对角线的30%。然后再另起一个绿色,渐变为蓝色,这2种颜色的跨度为对角线的30%到80%这一部分,也就是总跨度站了对角线的一半。最后蓝色渐变为黄色,这一段渐变效果在对角线上的跨度占了20%
注意这里position数组的元素个数应该和color数组的元素个数相同。具体效果图如下图2所示。
图2
参数7: Shader.TileMode,平铺模式,和BitmapShader一样,也分为3种模式且概念一样:
Shader.TileMode.CLAMP
Shader.TileMode.REPEAT
Shader.TileMode.MIRROR
(2)canvas.drawRect(0, 0, 800, 800, mPaint);
绘制一个矩形,对角线长度刚好和LinearGradient渐变的范围一样(对角线一样)。这时“线性渐变渲染 将 矩形图形加以渲染,效果如上图1(position为null)和图2所示(position不为null的情况)。
3. SweepGradient扫描式渐变渲染器
何为扫描式渐变渲染器,直观的给大家展示一下,如下图所示:
图3 扫描式渐变渲染器
实现这种各个颜色围绕圆心渐变的效果,代码如下:
-
package com.cb.paint_gradient;
-
import android.content.Context;
-
import android.graphics.Bitmap;
-
import android.graphics.BitmapShader;
-
import android.graphics.Canvas;
-
import android.graphics.Color;
-
import android.graphics.ComposeShader;
-
import android.graphics.LinearGradient;
-
import android.graphics.Matrix;
-
import android.graphics.Paint;
-
import android.graphics.RadialGradient;
-
import android.graphics.Rect;
-
import android.graphics.RectF;
-
import android.graphics.Shader;
-
import android.graphics.SweepGradient;
-
import android.graphics.drawable.BitmapDrawable;
-
import android.graphics.drawable.ShapeDrawable;
-
import android.graphics.drawable.shapes.OvalShape;
-
import android.support.annotation.Nullable;
-
import android.util.AttributeSet;
-
import android.view.View;
-
-
/**
-
* Created by xw.gao
-
*/
-
-
public class MyShaderView extends View {
-
private Paint mPaint;
-
private Bitmap mBitMap = null;
-
-
private int mWidth;
-
private int mHeight;
-
private int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW};
-
-
public MyShaderView(Context context, @Nullable AttributeSet attrs) {
-
super(context, attrs);
-
mBitMap = ((BitmapDrawable)getResources().getDrawable(R.drawable.head)).getBitmap();
-
mPaint = new Paint();
-
mWidth = mBitMap.getWidth();
-
mHeight = mBitMap.getHeight();
-
}
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
canvas.drawColor(Color.WHITE);
-
SweepGradient mSweepGradient = new SweepGradient(300, 300, mColors, null);
-
mPaint.setShader(mSweepGradient);
-
canvas.drawCircle(300, 300, 300, mPaint);
-
-
}
-
}
还是看2处代码:
(1)SweepGradient mSweepGradient = new SweepGradient(300,300,color,null);
mPaint.setShader(mSweepGradient);
这里生成了一个“扫描式渐变渲染器”,参数解释如下:
参数1和参数2是扫描圆心,
参数3 int[]color数组和之前的一样,可以有多种颜色之间的渐变来平分这个圆。
参数4:float[]position , 这个和之前一样是一个float[]position数组,为null则平分这个圆就如上图3所示,红-绿 占1/3; 绿-蓝 占1/3; 蓝-黄 占1/3. 如果不平分,假如设置float[]position = {0,0.3f,0.8f,1};,则效果会如下图4所示:红色-绿色这一对跨度为30%,绿色-蓝色这一对跨度为50%,最后蓝色-黄色这一对跨度为20%。
图4
注意:这次没有Shader.TileMode这个填充模式参数了。因为既然是圆周扫描,肯定要和图形(圆形)的大小一样。
(2)canvas.drawCircle(300, 300, 300, mPaint);,
绘制一个图形:“圆”;然后用带有SweepGradient渲染器的paint去渲染这个图形。这个图形的圆心(第1、2参数)和SweepGradient的圆心一样。第3个参数是圆的半径,决定了这个四彩缤纷的圆的大小,第4个参数是画笔。
至此SweepGradient (扫描式渐变渲染器)就介绍到这里。
4. RadialGradient环形(从中心向外辐射)渐变渲染
何为环形渐变,就是颜色从圆心向外不断扩散,最终成为多个颜色渐变的圆环,如下图效果:
图5
实现上述环形渐变RadialGradien的核心代码还是2处:
(1)RadialGradient mRadialGradient = new RadialGradient(300, 300, 100, mColors, null, Shader.TileMode.REPEAT); mPaint.setShader(mRadialGradient);
生成了一个环形渲染器,
参数1:圆心X坐标
参数2:圆心Y坐标
参数4:颜色数组:int[] mColors = {Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW}
参数3:圆的半径100,就是经过Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW这4种颜色的圆环总宽度。
参数5:position数组,和之前的一样。
参数6:平铺模式,选择repeat, 颜色会循环,从红-绿-蓝-黄,然后接着红-绿-蓝-黄...以此类推,直到贴满整个圆形
(2)canvas.drawCircle(300, 300, 300, mPaint);
这里绘制一个圆形,圆形的圆心和RadialGradient的圆心一样,圆形的半径是300,由于RadialGradient环形渲染的从是100,这说明将有3组【Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW】渐变,贴满这个圆形,效果如上图5所示。我们如果把圆形的半径改为100,同环形渐变RadialGradient的半径一样,那么只需1组【Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW】就可以把圆形贴满,效果如下图6:
图6
(3)改变平铺模式看一下效果:
---平铺模式改为:Shader.TileMode.CLAMP
-
RadialGradient mRadialGradient = new RadialGradient(300, 300, 100, mColors, null, Shader.TileMode.CLAMP);
-
mPaint.setShader(mRadialGradient);
-
canvas.drawCircle(300, 300, 300, mPaint);
运行效果如下:
我们发现当半径为100的RadialGradient不能贴满半径为300的圆形时,它会把RadialGradient的最后一个像素(黄色)拉伸至半径300. 模式Shader.TileMode.CLAMP就是这样,只拉伸最后一个像素,这点也适用于所有渲染器.,不只是RadialGradient。
---平铺模式改为:Shader.TileMode.MIRROR镜像模式
-
RadialGradient mRadialGradient = new RadialGradient(300, 300, 100, mColors, null, Shader.TileMode.MIRROR);
-
mPaint.setShader(mRadialGradient);
-
canvas.drawCircle(300, 300, 300, mPaint);
效果如下:
我们发现 【Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW】总共3组,相邻组互为镜像,从圆心向外扩散,3组顺序依次为:
【Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW】,【Color.YELLOW,Color.BLUE,Color.GREEN,Color.RED】,
【Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW】。
5. 组合渲染ComposeShader
ComposeShader就是把前面介绍的各渲染器组合起来,作用于同一图形,例如下面是把图片渲染器bitmapShader和线性渐变渲染器linearGradient组合起来,作用在同一矩形里,最终的效果是,“心”图片贴在矩形图形里,同时被linearGradient渐变渲染器着色为了绿色。
“心”图片为:
最终组合bitmapShader和bitmapShader,使矩形变为了如下图所示:
上述使用组合渲染ComposeShader的代码如下:
-
//创建BitmapShader,用以绘制心
-
mBitMap = ((BitmapDrawable)getResources().getDrawable(R.drawable.heart)).getBitmap();
-
BitmapShader bitmapShader = new BitmapShader(mBitMap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
-
LinearGradient linearGradient = new LinearGradient(0, 0, mWidth, mHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
-
//bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式
-
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
-
//将组合的composeShader作为画笔paint绘图所使用的shader
-
mPaint.setShader(composeShader);
-
//用composeShader绘制矩形区域
-
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
我们主要看一处核心代码,因为其它前面已经介绍过了。
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
第一个参数是 图片渲染器;第二个参数是线性渲染器,将要组合的就是这2个渲染器,共同作用于图形上。
第三个参数PorterDuff.Mode.MULTIPL:取两图层交集部分叠加后颜色。关于这个PorterDuffMode我们会在以后研究。
OK,至此大家对渲染器已经有所了解,下一篇我们再详细介绍渲染器的使用实例。
源码下载地址:https://download.csdn.net/download/gaoxiaoweiandy/12131538
二、实例开发
1. 雷达扫描
效果如下:
1.1 MainActivity.java代码:
-
package com.xwgao.radarview;
-
-
import android.os.Bundle;
-
import android.support.v7.app.AppCompatActivity;
-
import android.view.View;
-
-
-
-
public class MainActivity extends AppCompatActivity {
-
-
private RadarView mRadarView;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
-
mRadarView = (RadarView) findViewById(R.id.radarview);
-
}
-
-
public void start(View view){
-
mRadarView.startScan();
-
}
-
-
public void stop(View view){
-
mRadarView.stopScan();
-
}
-
}
其中R.id.radarview就是我们将要自定义的雷达扫描控件。start(View view)和stop(View view)是开始雷达扫描和停止扫描两个按钮。具体布局如下:
-
<?xml version="1.0" encoding="utf-8"?>
-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
xmlns:app="http://schemas.android.com/apk/res-auto"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
>
-
-
<com.xwgao.radarview.RadarView
-
android:id="@+id/radarview"
-
android:layout_width="300dp"
-
android:layout_height="300dp"
-
android:layout_centerInParent="true"
-
app:backgroundColor="#000000"
-
app:circleNum="4"
-
app:endColor="#aaff0000"
-
app:lineColor="#00ff00"
-
app:startColor="#aa0000ff"/>
-
-
<Button
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_alignParentLeft="true"
-
android:layout_alignParentBottom="true"
-
android:onClick="start"
-
android:text="开始" />
-
-
<Button
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_alignParentRight="true"
-
android:layout_alignParentBottom="true"
-
android:onClick="stop"
-
android:text="停止" />
-
-
</RelativeLayout>
接下来我们就看一下自定义雷达控件:RadarView如何实现。
1.2 RadarView自定义控件
要实现雷达RadarView自定义控件,我们都需要做什么工作? (1)雷达扫描渐变,使用SweepGradient来实现。 (2)旋转扫描:不断(用handler实现一个循环的定时器,每隔20ms)让Matrix矩阵来改变 “扫描式渐变”的角度。接下来我们贴出代码来分析。RadarView的代码如下:
-
package com.xwgao.radarview;
-
-
import android.content.Context;
-
import android.content.res.TypedArray;
-
import android.graphics.Canvas;
-
import android.graphics.Color;
-
import android.graphics.Matrix;
-
import android.graphics.Paint;
-
import android.graphics.Shader;
-
import android.graphics.SweepGradient;
-
import android.os.Handler;
-
import android.os.Message;
-
import android.util.AttributeSet;
-
import android.util.Log;
-
import android.view.View;
-
-
public class RadarView extends View {
-
private final String TAG = "RadarView";
-
-
private static final int MSG_WHAT = 1;
-
-
private static final int DELAY_TIME = 20;
-
-
//设置默认宽高,雷达一般都是圆形,所以我们下面取宽高会去Math.min(宽,高)
-
private final int DEFAULT_WIDTH = 200;
-
-
private final int DEFAULT_HEIGHT = 200;
-
//雷达的半径
-
private int mRadarRadius;
-
//雷达画笔
-
private Paint mRadarPaint;
-
//雷达底色画笔
-
private Paint mRadarBg;
-
//雷达圆圈的个数,默认4个
-
private int mCircleNum = 4;
-
//雷达线条的颜色,默认为白色
-
private int mCircleColor = Color.WHITE;
-
//雷达圆圈背景色
-
private int mRadarBgColor = Color.BLACK;
-
//paintShader
-
private Shader mRadarShader;
-
-
//雷达扫描时候的起始和终止颜色
-
private int mStartColor = 0x0000ff00;
-
-
private int mEndColor = 0xaa00ff00;
-
-
-
private Matrix mMatrix;
-
-
//旋转的角度
-
private int mRotate = 0;
-
-
private Handler mHandler = new Handler() {
-
@Override
-
public void handleMessage(Message msg) {
-
super.handleMessage(msg);
-
-
mRotate += 3;
-
postInvalidate();
-
-
mMatrix.reset();
-
mMatrix.preRotate(mRotate, 0, 0);
-
mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
-
}
-
};
-
-
public RadarView(Context context) {
-
this(context, null);
-
}
-
-
public RadarView(Context context, AttributeSet attrs) {
-
this(context, attrs, 0);
-
}
-
-
public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
init(context, attrs);
-
-
-
mRadarBg = new Paint(Paint.ANTI_ALIAS_FLAG); //设置抗锯齿
-
mRadarBg.setColor(mRadarBgColor); //画笔颜色
-
mRadarBg.setStyle(Paint.Style.FILL); //画实心圆
-
-
mRadarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //设置抗锯齿
-
mRadarPaint.setColor(mCircleColor); //画笔颜色
-
mRadarPaint.setStyle(Paint.Style.STROKE); //设置空心的画笔,只画圆边
-
mRadarPaint.setStrokeWidth(2); //画笔宽度
-
-
mRadarShader = new SweepGradient(0, 0, mStartColor, mEndColor);
-
-
mMatrix = new Matrix();
-
}
-
-
-
//初始化,拓展可设置参数供布局使用
-
private void init(Context context, AttributeSet attrs) {
-
if (attrs != null) {
-
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RadarView);
-
mStartColor = ta.getColor(R.styleable.RadarView_startColor, mStartColor);
-
mEndColor = ta.getColor(R.styleable.RadarView_endColor, mEndColor);
-
mRadarBgColor = ta.getColor(R.styleable.RadarView_backgroundColor, mRadarBgColor);
-
mCircleColor = ta.getColor(R.styleable.RadarView_lineColor, mCircleColor);
-
mCircleNum = ta.getInteger(R.styleable.RadarView_circleNum, mCircleNum);
-
ta.recycle();
-
}
-
}
-
-
-
@Override
-
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-
super.onSizeChanged(w, h, oldw, oldh);
-
mRadarRadius = Math.min(w / 2, h / 2);
-
-
//Log.d(TAG, "onSizeChanged");
-
}
-
-
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-
int width = measureSize(1, DEFAULT_WIDTH, widthMeasureSpec);
-
int height = measureSize(0, DEFAULT_HEIGHT, heightMeasureSpec);
-
Log.i(TAG,"width="+width+",height="+height);
-
-
//取最大的 宽|高
-
int measureSize = Math.max(width, height);
-
setMeasuredDimension(measureSize, measureSize);
-
}
-
-
-
-
/**
-
* 测绘measure
-
*
-
* @param specType 1为宽, 其他为高
-
* @param contentSize 默认值
-
*/
-
private int measureSize(int specType, int contentSize, int measureSpec) {
-
int result = 0;
-
//获取测量的模式和Size
-
int specMode = MeasureSpec.getMode(measureSpec);
-
int specSize = MeasureSpec.getSize(measureSpec);
-
-
if (specMode == MeasureSpec.EXACTLY) {
-
result = Math.max(contentSize, specSize);
-
} else if (specMode == MeasureSpec.AT_MOST){
-
result = contentSize;
-
-
if (specType == 1) {
-
// 根据传人方式计算宽
-
result += (getPaddingLeft() + getPaddingRight());
-
} else {
-
// 根据传人方式计算高
-
result += (getPaddingTop() + getPaddingBottom());
-
}
-
}
-
-
return result;
-
-
}
-
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
-
Log.d(TAG, "onDraw " + mRotate);
-
-
mRadarBg.setShader(null);
-
-
//将画板移动到屏幕的中心点
-
canvas.translate(mRadarRadius, mRadarRadius);
-
//绘制底色,让雷达的线看起来更清晰
-
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
-
//画圆圈
-
for (int i = 1; i <= mCircleNum; i++) {
-
canvas.drawCircle(0, 0, (float) (i * 1.0 / mCircleNum * mRadarRadius), mRadarPaint);
-
}
-
//绘制雷达基线 x轴
-
canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint);
-
//绘制雷达基线 y轴
-
canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint);
-
-
// canvas.rotate(mRotate,0,0);
-
//设置颜色渐变从透明到不透明
-
mRadarBg.setShader(mRadarShader);
-
canvas.concat(mMatrix);
-
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
-
}
-
-
-
public void startScan() {
-
mHandler.removeMessages(MSG_WHAT);
-
mHandler.sendEmptyMessage(MSG_WHAT);
-
}
-
-
public void stopScan() {
-
mHandler.removeMessages(MSG_WHAT);
-
}
-
}
-
我们主要看一下onDraw函数,
-
-
(1)canvas.translate(mRadarRadius, mRadarRadius);这个将画布水平和垂直方向各移动 “半径长度”mRadarRadius。
这样的话canvas的原点(0,0)就是雷达圆的圆心了。在绘制雷达圆圈的时候以(0,0)为圆心绘制出的圆自然也不会超出canvas屏幕。
(2)canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);绘制雷达圆圈的背景,默认是黑色,将来在这个黑色背景上绘制“扫描式渐变”,
白色圆圈,雷达“十字”线等。
-
(3)//绘制白色圆圈
-
for (int i = 1; i <= mCircleNum; i++) {
-
canvas.drawCircle(0, 0, (float) (i * 1.0 / mCircleNum * mRadarRadius), mRadarPaint);
-
}
(4) 绘制雷达 水平+垂直方向的那个“十字线”
-
//绘制雷达基线 x轴
-
canvas.drawLine(-mRadarRadius, 0, mRadarRadius, 0, mRadarPaint);
-
//绘制雷达基线 y轴
-
canvas.drawLine(0, mRadarRadius, 0, -mRadarRadius, mRadarPaint);
(5)绘制“扫描式渐变”
-
mRadarBg.setShader(mRadarShader);
-
canvas.concat(mMatrix);
-
canvas.drawCircle(0, 0, mRadarRadius, mRadarBg);
其中mRadarShader就是“扫描式渲染器",它的颜色渐变是从红色渐变到蓝色:
mRadarShader = new SweepGradient(0, 0, mStartColor, mEndColor);
mMatrix是用来改变”扫描“角度的。canvas.concat(mMatrix);通过这行代码,让mMatrix和canvas画布关联起来,我们定于了一个handler不断的发送msg,来让mMatrix不断改变角度,从而旋转了画布canvas,形成”扫描效果“。定于的hander处理消息的代码如下:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mRotate += 3;
postInvalidate();
mMatrix.reset();
mMatrix.preRotate(mRotate, 0, 0);
mHandler.sendEmptyMessageDelayed(MSG_WHAT, DELAY_TIME);
}
};
在这里,我们每次改变角度mRotate后,让mMatrix.reset(),让画布先恢复回去,然后再重新源码canvas到最新的角度: mMatrix.preRotate(mRotate, 0, 0);。当然从恢复到再次重新旋转之间的空隙时间,用户眼睛是感觉不到的,并且是每隔20ms改变一次角度(+3),所以最终的效果还是连续的。如果不恢复回去的话,你可以试试看,它会以当前角度位置为起点,跳跃式的增量 value (mRotate += 3)度,会是跳跃的感觉。
Ok,至此雷达扫描效果就先介绍到这里。
第(二)节,雷达扫描源码地址:https://download.csdn.net/download/gaoxiaoweiandy/12172599
第(一)节源码下载地址:https://download.csdn.net/download/gaoxiaoweiandy/12131538
2. 辐射渐变:RadiaGradient
现在我们用RadiaGradient实现以下效果的按钮,当按下按钮时,会在按钮上绘制一个辐射渐变圆,类似于第(一)节中的“4. RadialGradient环形(从中心向外辐射)渐变渲染”。
2.1 原理:
当触摸按钮时,会执行onTouch事件,在这里面根据触摸的x,y值,来改变“辐射渐变圆”的圆心位置。当执行UP抬起手指事件时,执行一个动画,让圆的半径从DEFAULT_RADIUS逐渐扩展到整个按钮的宽度,类似于android Material Design里的button水波纹效果。
2.2代码如下:
-
package com.xiaowei.paint_radialgradient;
-
import android.animation.Animator;
-
import android.animation.ObjectAnimator;
-
import android.content.Context;
-
import android.graphics.Canvas;
-
import android.graphics.Paint;
-
import android.graphics.RadialGradient;
-
import android.graphics.Shader;
-
import android.util.AttributeSet;
-
import android.view.MotionEvent;
-
import android.view.animation.AccelerateInterpolator;
-
import android.widget.Button;
-
-
public class RippleView extends Button {
-
// 点击位置
-
private int mX, mY;
-
-
private ObjectAnimator mAnimator;
-
// 默认半径
-
private int DEFAULT_RADIUS = 100;
-
-
private int mCurRadius = 0;
-
-
private RadialGradient mRadialGradient;
-
-
private Paint mPaint;
-
-
public RippleView(Context context) {
-
super(context);
-
init();
-
}
-
-
public RippleView(Context context, AttributeSet attrs) {
-
super(context, attrs);
-
init();
-
}
-
-
private void init() {
-
// 禁用硬件加速
-
setLayerType(LAYER_TYPE_SOFTWARE,null);
-
mPaint = new Paint();
-
}
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
-
if (mX != event.getX() || mY != mY) {
-
mX = (int) event.getX();
-
mY = (int) event.getY();
-
-
setRadius(DEFAULT_RADIUS);
-
}
-
switch (event.getAction()){
-
case MotionEvent.ACTION_DOWN:
-
return true;
-
-
case MotionEvent.ACTION_UP:
-
{
-
if (mAnimator != null && mAnimator.isRunning()) {
-
mAnimator.cancel();
-
}
-
-
if (mAnimator == null) {
-
mAnimator = ObjectAnimator.ofInt(this,"radius",DEFAULT_RADIUS, getWidth());
-
}
-
-
mAnimator.setInterpolator(new AccelerateInterpolator());
-
mAnimator.addListener(new Animator.AnimatorListener() {
-
@Override
-
public void onAnimationStart(Animator animation) {
-
-
}
-
-
@Override
-
public void onAnimationEnd(Animator animation) {
-
setRadius(0);
-
}
-
-
@Override
-
public void onAnimationCancel(Animator animation) {
-
-
}
-
-
@Override
-
public void onAnimationRepeat(Animator animation) {
-
-
}
-
});
-
mAnimator.start();
-
}
-
}
-
-
return super.onTouchEvent(event);
-
}
-
-
public void setRadius(final int radius) {
-
mCurRadius = radius;
-
if (mCurRadius > 0) {
-
mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
-
mPaint.setShader(mRadialGradient);
-
}
-
postInvalidate();
-
}
-
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
-
canvas.drawCircle(mX, mY, mCurRadius, mPaint);
-
}
-
}
代码分析:
首先我们看到onDraw里绘制了一个圆。然后我们看看mPaint是如何配置的。在onTouchEnvent的setRadius里为mPaint配置了一个“辐射性渐变”,圆心刚好是onTouchEvent触摸的x,y, 我们把setRadius代码摘取出来:
-
public void setRadius(final int radius) {
-
mCurRadius = radius;
-
if (mCurRadius > 0) {
-
mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF, 0xFF58FAAC, Shader.TileMode.CLAMP);
-
mPaint.setShader(mRadialGradient);
-
}
-
postInvalidate();
-
}
mRadialGradient 是一个辐射渐变实例,圆心是mx,mY,即触摸点,颜色从0x00FFFFFF渐变到0xFF58FAAC,循环这个渐变直到延伸到整个半径,铺满这个圆,因为TileMode设置的是CLAMP. 最终调用postInvalidate触发onDraw的执行。也就是说我们每触摸一次按钮,就会重新配置一次mPaint,因为辐射渐变的圆心位置变了,进而会重新绘制一次。
在 case MotionEvent.ACTION_UP事件里,我们就会执行水波纹扩散动画:
-
if (mAnimator == null) {
-
mAnimator = ObjectAnimator.ofInt(this,"radius",DEFAULT_RADIUS, getWidth());
-
}
这个动画使 “辐射渐变”的半径从 DEFAULT_RADIUS逐渐扩展到按钮的宽度getWidth().
这里注意一下,"radius"这个属性值,是我们为RippleView这个自定义按钮自定义的属性。必须得有setRadius方法,否则属性“radius”不能识别,即不能被系统看做是RippleView的一个属性。
Ok,剩下的代码相信大家都能理解,“辐射圆渐变”实例就先讲解到这里。
“辐射圆渐变”源码:https://download.csdn.net/download/gaoxiaoweiandy/12202323
3. BitmapShader实例
3.1 放大镜效果,如下git所示:
放大镜 自定义控件ZoomView代码如下:
-
package com.cb.paint_gradient;
-
-
import android.content.Context;
-
import android.graphics.Bitmap;
-
import android.graphics.BitmapFactory;
-
import android.graphics.BitmapShader;
-
import android.graphics.Canvas;
-
import android.graphics.Matrix;
-
import android.graphics.Shader;
-
import android.graphics.drawable.ShapeDrawable;
-
import android.graphics.drawable.shapes.OvalShape;
-
import android.view.MotionEvent;
-
import android.view.View;
-
-
/**
-
*
-
*/
-
-
public class ZoomImageView extends View {
-
-
//放大倍数
-
private static final int FACTOR = 2;
-
//放大镜的半径
-
private static final int RADIUS = 100;
-
// 原图
-
private Bitmap mBitmap;
-
// 放大后的图
-
private Bitmap mBitmapScale;
-
// 制作的圆形的图片(放大的局部),盖在Canvas上面
-
private ShapeDrawable mShapeDrawable;
-
-
private Matrix mMatrix;
-
-
public ZoomImageView(Context context) {
-
super(context);
-
-
mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.hlw);
-
mBitmapScale = mBitmap;
-
//放大后的整个图片
-
mBitmapScale = Bitmap.createScaledBitmap(mBitmapScale,mBitmapScale.getWidth() * FACTOR,
-
mBitmapScale.getHeight() * FACTOR,true);
-
BitmapShader bitmapShader = new BitmapShader(mBitmapScale, Shader.TileMode.CLAMP,
-
Shader.TileMode.CLAMP);
-
-
mShapeDrawable = new ShapeDrawable(new OvalShape());
-
mShapeDrawable.getPaint().setShader(bitmapShader);
-
// 切出矩形区域,用来画圆(内切圆)
-
mShapeDrawable.setBounds(0,0,RADIUS * 2,RADIUS * 2);
-
-
mMatrix = new Matrix();
-
}
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
-
// 1、画原图
-
canvas.drawBitmap(mBitmap, 0 , 0 , null);
-
-
// 2、画放大镜的图
-
mShapeDrawable.draw(canvas);
-
}
-
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
int x = (int) event.getX();
-
int y = (int) event.getY();
-
-
// 将放大的图片往相反的方向挪动
-
mMatrix.setTranslate(RADIUS - x * FACTOR, RADIUS - y *FACTOR);
-
mShapeDrawable.getPaint().getShader().setLocalMatrix(mMatrix);
-
// 切出手势区域点位置的圆
-
mShapeDrawable.setBounds(x-RADIUS,y - RADIUS, x + RADIUS, y + RADIUS);
-
invalidate();
-
return true;
-
}
-
}
然后,MainActivity.java里加载这个ZoomView控件,并显示,代码如下:
-
package com.cb.paint_gradient;
-
import android.support.v7.app.AppCompatActivity;
-
import android.os.Bundle;
-
public class MainActivity extends AppCompatActivity {
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
//setContentView(R.layout.activity_main);
-
ZoomImageView view = new ZoomImageView(this);
-
setContentView(view);
-
}
-
}
3.2 文字跑马灯效果
自定义View代码如下:
-
package com.cb.paint_gradient;
-
import android.content.Context;
-
import android.graphics.Canvas;
-
import android.graphics.LinearGradient;
-
import android.graphics.Matrix;
-
import android.graphics.Shader;
-
import android.support.annotation.Nullable;
-
import android.text.TextPaint;
-
import android.util.AttributeSet;
-
import android.widget.TextView;
-
-
public class LinearGradientTextView extends TextView{
-
private TextPaint mPaint;
-
private LinearGradient mLinearGradient ;
-
private Matrix mMatrix;
-
private float mTranslate;
-
private float DELTAX = 20;
-
public LinearGradientTextView(Context context) {
-
super(context);
-
}
-
public LinearGradientTextView(Context context, @Nullable AttributeSet attrs) {
-
super(context, attrs);
-
}
-
-
@Override
-
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-
super.onSizeChanged(w, h, oldw, oldh);
-
// 拿到TextView的画笔
-
mPaint = getPaint();
-
String text = getText().toString();
-
float textWith = mPaint.measureText(text);
-
// 3个文字的宽度
-
int gradientSize = (int) (textWith / text.length() * 3);
-
-
// 从左边-gradientSize开始,即左边距离文字gradientSize开始渐变
-
mLinearGradient = new LinearGradient(-gradientSize,0,0,0,new int[]{
-
0x22ffffff, 0xffffffff, 0x22ffffff},null, Shader.TileMode.CLAMP
-
);
-
mPaint.setShader(mLinearGradient);
-
}
-
-
@Override
-
protected void onDraw(Canvas canvas) {
-
super.onDraw(canvas);
-
mTranslate += DELTAX;
-
float textWidth = getPaint().measureText(getText().toString());
-
if(mTranslate > textWidth + 1 || mTranslate < 1){
-
DELTAX = - DELTAX;
-
}
-
-
mMatrix = new Matrix();
-
mMatrix.setTranslate(mTranslate, 0);
-
mLinearGradient.setLocalMatrix(mMatrix);
-
postInvalidateDelayed(50);
-
-
}
-
}
MainActivity.java:
-
public class MainActivity extends AppCompatActivity {
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
}
-
}
activity_main.xml:
-
<?xml version="1.0" encoding="utf-8"?>
-
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:background="#000"
-
>
-
<com.cb.paint_gradient.LinearGradientTextView
-
android:layout_width="match_parent"
-
android:layout_height="wrap_content"
-
android:textSize="30sp"
-
android:textColor="#666666"
-
android:text="武汉加油,中国加油"/>-
-
-
</android.support.constraint.ConstraintLayout>
文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/104127732
- 点赞
- 收藏
- 关注作者
评论(0)