Android APP完整基础教程(16)图形系统-图像特效

该部分主要基于Canvas的绘图系统介绍一些常见的特效。

1 扭曲特效

这里使用Canvas的drawBitmapmesh对图像的局部进行扭曲特效处理。该效果主要是在APP上显示“水波荡漾”、“红旗飘扬”等扭曲效果,非常灵活。drawBitmapmesh定义如下:

public void drawBitmapMesh (Bitmap bitmap, 
                int meshWidth,int meshHeight, 
                float[] verts,int vertOffset, 
                int[] colors,int colorOffset,Paint paint)

主要有几个关键参数(其他参数相对好理解一些):

  • meshWidth 和 meshHeight:在横向/纵向把图片切分成多少个格子。
  • verts:该参数是一个长度len为(meshWidth+1)*(meshHeight+1)*2的数组,记录了扭曲后位图的各个顶点,记录格式为:(x1,y1)、(x2,y2)、(x3,y3)、。。。该方法主要是通过这些数组元素来控制对bitmap位图的扭曲效果(在下面实战demo中会进一步了解)。

drawBitmapmesh的实际扭曲效果图如下所示:

Android APP完整基础教程(16)图形系统-图像特效

关于drawBitmapmesh方法更多解读可参照文档: Android Canvas drawBitmapMesh详解

@2 drawBitmapmesh实战(drawBitmapmesh效果展示)

实现功能:触摸屏幕,在触摸处展示drawBitmapmesh的扭曲效果。程序运行效果如下所示:

Android APP完整基础教程(16)图形系统-图像特效

关于该程序,自定义View的关键代码如下所示:

public class MyView extends View {
    private static final String TAG = "MyView";
    private Matrix initMatrix = new Matrix();
    private int h,w;
    //为drawBitmapMesh构建的相关变量,start
    private final static int WIDTH =20;
    private final static int HEIGHT =20;
    private final static int COUNT = (WIDTH+1)*(HEIGHT+1);
    private float[] verts = new float[COUNT*2];
    private float[] origs = new float[COUNT*2];
    //为drawBitmapMesh构建的相关变量,end
    public Bitmap bitmap = null;
    float offsetStartX=0.0f,offsetStartY=0.0f;//居中显示调整

    public MyView(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context){
        //1 获取bitmap
        initMatrix.reset();
        initMatrix.setScale(1.0f,1.0f);
        try {
            InputStream isImage = context.getAssets().open("test6.png");
            bitmap = BitmapFactory.decodeStream(isImage);
            w = bitmap.getWidth();
            h = bitmap.getHeight();
            bitmap = Bitmap.createBitmap(bitmap,0,0, w, h, initMatrix, true);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //2 按照drawBitmapMesh规定方式构建origs数组(处理数据)和verts数组(保存原始数据)
        int index=0;
        for(int i=0;i<HEIGHT+1;i++){
            float fy=(h*i)/HEIGHT;
            for(int j=0;j<WIDTH+1;j++){
                float fx=(w*j)/WIDTH;
                //记录格式为:(x1,y1)、(x2,y2)、(x3,y3),以此类推
                origs[index*2+0]=verts[index*2+0]=fx;
                origs[index*2+1]=verts[index*2+1]=fy;
                index++;
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        //居中显示
        offsetStartX = (canvas.getWidth()-bitmap.getWidth())*1.0f/2;
        offsetStartY = (canvas.getHeight()-bitmap.getHeight())*1.0f/2;
        canvas.translate(offsetStartX,offsetStartY);
        //5 bitmap绘制,当vert数组变化时该图片中局部会做相应的调整。
        canvas.drawBitmapMesh(bitmap,WIDTH,HEIGHT,verts,0,null,0,null);
    }

    private void wrap(float x,float y){
        //4 关键算法,扭曲效果设计,根据输入的事件坐标构建扭曲效果。
        for(int i=0;i<COUNT*2;i+=2){
            float dx = x-origs[i];
            float dy = y-origs[i+1];
            float d = (float)Math.sqrt(dx*dx+dy*dy);
            float pull = 200000.f/(d*d*d);
            if(pull>=1){
                verts[i] = x;
                verts[i+1] = y;
            }else{
                verts[i] = origs[i]+dx*pull;
                verts[i+1] = origs[i+1]+dy*pull;
            }
        }
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //3 为配合居中显示,这里做了坐标校正。
        wrap(event.getX()-offsetStartX,event.getY()-offsetStartY);
        return true;
    }
}

在MainActivity中实现代码为:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
}

2 shader特效

@1 shader概念基础与派生类解读

这里主要是使用Paint画笔的填充模式展示shader的填充效果。在Android APP中,Shader类(更多关于Shader类解读查看文档:Android Shader类详解)是一个抽象类,它的派生类如下:

shader是着色器的含义(就像皮肤一样,只不过我们看到的一个人穿的衣服还要依赖于环境,比如光照、阴影等,也就是说shader可以理解为 衣服在一个人身上 整体呈现的最终效果),更多关于shader的解读可查看文章:OpenGL基础(01)基础知识中的第二部分即可。

@2 shader效果实战(展示5种shader派生类特效)

实现功能:通过按键响应5种shader效果,效果如下所示:

Android APP完整基础教程(16)图形系统-图像特效

关于该程序,自定义View的关键代码如下所示:

public class MyView extends View {
    private static final String TAG = "MyView";
    public Paint paint = new Paint();
    private Path path = new Path();

    public MyView(Context context,AttributeSet attrs){
        super(context,attrs);
        init();
    }

    private void init(){
        paint.setAntiAlias(true);//抗锯齿
        paint.setColor(Color.RED);//画笔颜色
        paint.setStyle(Paint.Style.FILL);//填充模式
        paint.setStrokeWidth(4f);//设置画笔粗细度
        paint.setTextSize(48f);
    }

    public void setShader(Shader shader){
        paint.setShader(shader);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        drawsth(canvas,paint,path,440f);
    }
	
    //测试:绘制基本图形、Path不规则图形和文字
    private void drawsth(Canvas canvas, Paint paint,Path path,float offset){

        //paint绘制圆形,参数:圆心坐标(x,y)半径r,画笔paint
        canvas.drawCircle(100f+offset,100f,90f,paint);
        //paint绘制矩形,参数:左上角坐标(x,y),右下角坐标(x,y),画笔paint
        canvas.drawRect(10f+offset,200f,210f+offset,400f,paint);
        //paint绘制圆角矩形,参数:左上角坐标(x,y),右下角坐标(x,y),x方向上的圆角半径,y方向上的圆角半径,画笔paint
        canvas.drawRoundRect(10f+offset,410f,210f+offset,610f,40f,40f,paint);
        //paint绘制椭圆,参数:左上角坐标(x,y),右下角坐标(x,y)[参数表示矩形内切的椭圆],画笔paint
        canvas.drawOval(10f+offset,620f,210f+offset,720f,paint);

        //Path绘制三角形,moveto表示第1个坐标,lineto分别表示第2、3个坐标。以此类推。
        path.moveTo(110f+offset,730f);
        path.lineTo(10f+offset,930f);
        path.lineTo(210f+offset,930f);
        path.close();
        canvas.drawPath(path,paint);
        //Path绘制不规则多点多边形
        path.moveTo(10f+offset,950f);
        path.lineTo(50f+offset,1000f);
        path.lineTo(200f+offset,970f);
        path.lineTo(150f+offset,1070f);
        path.lineTo(10f+offset,1110f);
        path.lineTo(210f+offset,1130f);
        path.close();
        canvas.drawPath(path,paint);

        //注意:这个坐标(x,y)并不是文字的左上角,而是一个与左下角比较接近的位置。
        canvas.drawText("测试文字",10f+offset,1190f,paint);
    }
}

在MainActivity中实现代码如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static String TAG = "MainActivity";
    private Shader[] shaders = new Shader[5];
    private int[] colors = new int[]{Color.RED,Color.GREEN,Color.BLUE};
    private MyView myView;
    private Button btn1,btn2,btn3,btn4,btn5;
    private Bitmap bitmap = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.shaderlayout);
        Button btn1 = findViewById(R.id.btn1);
        Button btn2 = findViewById(R.id.btn2);
        Button btn3 = findViewById(R.id.btn3);
        Button btn4 = findViewById(R.id.btn4);
        Button btn5 = findViewById(R.id.btn5);
        myView = findViewById(R.id.myView);

        try {
            InputStream isImage = getAssets().open("test6.png");
            bitmap = BitmapFactory.decodeStream(isImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
		//创建5种shader的派生类对象,分别测试效果
        shaders[0] = new BitmapShader(bitmap,Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
        shaders[1] = new LinearGradient(0f,0f,100f,100f,colors,null, Shader.TileMode.REPEAT);
        shaders[2] = new RadialGradient(100f,100f,80f,colors,null, Shader.TileMode.REPEAT);
        shaders[3] = new SweepGradient(160f,160f,colors,null);
        shaders[4] = new ComposeShader(shaders[1],shaders[2], PorterDuff.Mode.ADD);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn1:myView.setShader(shaders[0]);break;
            case R.id.btn2:myView.setShader(shaders[1]);break;
            case R.id.btn3:myView.setShader(shaders[2]);break;
            case R.id.btn4:myView.setShader(shaders[3]);break;
            case R.id.btn5:myView.setShader(shaders[4]);break;
            default:break;
        }
    }
}

关于该程序,shaderlayout.xml 布局参考文件如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:visibility="visible">

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/BitmapShader" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/LinearGradient" />

        <Button
            android:id="@+id/btn3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/RadialGradient" />

        <Button
            android:id="@+id/btn4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/SweepGradient" />

        <Button
            android:id="@+id/btn5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="20"
            android:onClick="onClick"
            android:text="@string/ComposeGradient" />
    </LinearLayout>

    <com.ags.myapplication.MyView
        android:id="@+id/myView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/ic_launcher" />
</LinearLayout>

上一篇:jmeter参数化


下一篇:Beyond Compare