Android 图形特效处理

2022-02-14  本文已影响0人  gaookey
image.png

使用 Matrix 控制变换

Matrix 是 Android 提供的一个矩阵工具类,它本身并不能对图形或组件进行变换,但它可与其他 API 结合来控制图形、组件的变换。

使用 Matrix 控制图形或组件变换的步骤:

  1. 获取 Matrix 对象,该 Matrix 对象既可新创建,也可直接获取其它对象内封装的 Matrix 。
  2. 调用 Matrix 的方法进行平移、旋转、缩放、倾斜等。
  3. 将程序对 Matrix 所做的变换应用到指定图形或组件。

MyView

public class MyView extends View {
    // 初始的图片资源
    private Bitmap bitmap;
    // Matrix 实例
    private Matrix bmMatrix = new Matrix();
    // 设置倾斜度
    float sx;
    // 位图宽和高
    private int bmWidth;
    private int bmHeight;
    // 缩放比例
    float scale = 1.0f;
    // 判断缩放还是旋转
    boolean isScale;

    public MyView(Context context, AttributeSet set) {
        super(context, set);
        // 获得位图
        bitmap = ((BitmapDrawable) context.getResources().getDrawable(
                R.drawable.a, context.getTheme())).getBitmap();
        // 获得位图宽
        bmWidth = bitmap.getWidth();
        // 获得位图高
        bmHeight = bitmap.getHeight();
        // 使当前视图获得焦点
        this.setFocusable(true);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 重置Matrix
        bmMatrix.reset();
        if (!isScale) {
            // 倾斜Matrix
            bmMatrix.setSkew(sx, 0f);
        } else {
            // 缩放Matrix
            bmMatrix.setScale(scale, scale);
        }
        // 根据原始位图和Matrix创建新图片
        Bitmap bitmap2 = Bitmap.createBitmap(bitmap,
                0, 0, bmWidth, bmHeight, bmMatrix, true);
        // 绘制新位图
        canvas.drawBitmap(bitmap2, bmMatrix, null);
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        GridLayout grid = findViewById(R.id.grid);
        MyView mv = findViewById(R.id.mv);
        String[] bnArray = getResources().getStringArray(R.array.bnArray);
        for (String title : bnArray) {
            Button bn = new Button(this);
            bn.setText(title);
            grid.addView(bn);
            bn.setOnClickListener(view -> {
                switch (getIndex(bnArray, bn.getText())) {
                    // 向左倾斜
                    case 0:
                        mv.isScale = false;
                        mv.sx += 0.1f;
                        mv.postInvalidate();
                        break;
                    // 向右倾斜
                    case 1:
                        mv.isScale = false;
                        mv.sx -= 0.1f;
                        mv.postInvalidate();
                        break;
                    // 放大
                    case 2:
                        mv.isScale = true;
                        if (mv.scale < 2.0) mv.scale += 0.1f;
                        mv.postInvalidate();
                        break;
                    // 缩小
                    case 3:
                        mv.isScale = true;
                        if (mv.scale > 0.5) mv.scale -= 0.1f;
                        mv.postInvalidate();
                        break;
                }
            });
        }
    }

    // 定义一个工具方法,获取元素在数组中的出现次数
    private static <T> int getIndex(T[] arr, T target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target || arr[i].equals(target)) {
                return i;
            }
        }
        return -1;
    }
}

layout/activity_main.xml

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.myapplication.MyView
        android:id="@+id/mv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" />

    <GridLayout
        android:id="@+id/grid"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="200dp"
        android:rowCount="4"
        tools:ignore="MissingConstraints" />

</RelativeLayout>

values/arrays.xml

<resources>
    <string-array name="bnArray">
        <item>左倾斜</item>
        <item>右倾斜</item>
        <item>放大</item>
        <item>缩小</item>
    </string-array>
</resources>
image.png

使用 drawBitmapMesh 扭曲图像

Canvas 提供了一个 drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight, @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset, @Nullable Paint paint) 方法,该方法可以对 bitmap 进行扭曲。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this, R.drawable.image));
    }

    private class MyView extends View {
        // 定义两个常量,这两个常量指定该图片横向、纵向上都被划分为20格
        private final static int WIDTH = 20;
        private final static int HEIGHT = 20;
        // 记录该图片上包含441个顶点
        private final static int COUNT = (WIDTH + 1) * (HEIGHT + 1);
        // 定义一个数组,保存Bitmap上的21 * 21个点的坐标
        private float[] verts = new float[COUNT * 2];
        // 定义一个数组,记录Bitmap上的21 * 21个点经过扭曲后的坐标
        // 对图片进行扭曲的关键就是修改该数组里元素的值
        private float[] orig = new float[COUNT * 2];
        private Bitmap bitmap;

        MyView(Context context, int drawableId) {
            super(context);
            setFocusable(true);
            // 根据指定资源加载图片
            bitmap = BitmapFactory.decodeResource(getResources(), drawableId);
            // 获取图片宽度、高度
            float bitmapWidth = bitmap.getWidth();
            float bitmapHeight = bitmap.getHeight();
            int index = 0;

            //重新计算图片尺寸
            int[] screenSize = getScreenSize(context);
            if (bitmapWidth > bitmapHeight) {
                float ratio = (float) bitmapWidth / (float) bitmapHeight;
                bitmapWidth = (float) screenSize[0];
                bitmapHeight = (float) (screenSize[0] / ratio);
            } else {
                Double ratio = (double) bitmapHeight / (double) bitmapWidth;
                bitmapHeight = (float) screenSize[1];
                bitmapWidth = (float) (screenSize[1] / ratio);
            }


            for (int y = 0; y <= HEIGHT; y++) {
                float fy = bitmapHeight * y / HEIGHT;
                for (int x = 0; x <= WIDTH; x++) {
                    float fx = bitmapWidth * x / WIDTH;
                    // 初始化orig、verts数组。初始化后,orig、verts
                    // 两个数组均匀地保存了21 * 21个点的x,y坐标
                    verts[index * 2] = fx;
                    orig[index * 2] = verts[index * 2];
                    verts[index * 2 + 1] = fy;
                    orig[index * 2 + 1] = verts[index * 2 + 1];
                    index += 1;
                }
            }
            // 设置背景色
            setBackgroundColor(Color.WHITE);
        }

        @Override
        public void onDraw(Canvas canvas) {
            // 对bitmap按verts数组进行扭曲
            // 从第一个点(由第5个参数0控制)开始扭曲
            canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
        }

        // 工具方法,用于根据触摸事件的位置计算verts数组里各元素的值
        private void warp(float cx, float cy) {
            for (int i = 0; i < COUNT * 2; i += 2) {
                float dx = cx - orig[i];
                float dy = cy - orig[i + 1];
                float dd = dx * dx + dy * dy;
                // 计算每个坐标点与当前点(cx, cy)之间的距离
                float d = (float) Math.sqrt(dd);
                // 计算扭曲度,距离当前点(cx, cy)越远,扭曲度越小
                float pull = 200000 / (dd * d);
                // 对verts数组(保存bitmap上21 * 21个点经过扭曲后的坐标)重新赋值
                if (pull >= 1) {
                    verts[i] = cx;
                    verts[i + 1] = cy;
                } else {
                    // 控制各顶点向触摸事件发生点偏移
                    verts[i] = orig[i] + dx * pull;
                    verts[i + 1] = orig[i + 1] + dy * pull;
                }
            }
            // 通知View组件重绘
            invalidate();
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // 调用warp方法根据触摸屏事件的坐标点来扭曲verts数组
            warp(event.getX(), event.getY());
            return true;
        }

        /**
         * 获取屏幕宽度
         */
        private int[] getScreenSize(Context context) {
            DisplayMetrics dm = context.getResources().getDisplayMetrics();
            int size[] = {dm.widthPixels, dm.heightPixels};
            return size;
        }
    }
}
image.gif

使用 Shader 填充图形

MyView

public class MyView extends View {
    Paint paint = new Paint();

    public MyView(Context context, AttributeSet set) {
        super(context, set);
        paint.setColor(Color.RED);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 使用指定Paint对象画矩形
        canvas.drawRect(0f, 0f, getWidth(), getHeight(), paint);
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {
    // 声明Shader数组
    private Shader[] shaders = new Shader[5];
    // 声明颜色数组
    private int[] colors;
    private MyView myView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myView = findViewById(R.id.my_view);
        // 获得Bitmap实例
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.water);
        // 设置渐变的颜色组,也就是按红、绿、蓝的方式渐变
        colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
        // 实例化BitmapShader,x坐标方向重复图形,y坐标方向镜像图形
        shaders[0] = new BitmapShader(bm, Shader.TileMode.REPEAT,
                Shader.TileMode.MIRROR);
        // 实例化LinearGradient
        shaders[1] = new LinearGradient(0f, 0f, 100f, 100f,
                colors, null, Shader.TileMode.REPEAT);
        // 实例化RadialGradient
        shaders[2] = new RadialGradient(100f, 100f, 80f,
                colors, null, Shader.TileMode.REPEAT);
        // 实例化SweepGradient
        shaders[3] = new SweepGradient(160f, 160f, colors, null);
        // 实例化ComposeShader
        shaders[4] = new ComposeShader(shaders[1], shaders[2], PorterDuff.Mode.ADD);
        Button bn1 = findViewById(R.id.bn1);
        Button bn2 = findViewById(R.id.bn2);
        Button bn3 = findViewById(R.id.bn3);
        Button bn4 = findViewById(R.id.bn4);
        Button bn5 = findViewById(R.id.bn5);
        View.OnClickListener listener = source -> {
            int ID = source.getId();

            if (ID == R.id.bn1) {
                myView.paint.setShader(shaders[0]);
            } else if (ID == R.id.bn2) {
                myView.paint.setShader(shaders[1]);
            } else if (ID == R.id.bn3) {
                myView.paint.setShader(shaders[2]);
            } else if (ID == R.id.bn4) {
                myView.paint.setShader(shaders[3]);
            } else if (ID == R.id.bn5) {
                myView.paint.setShader(shaders[4]);
            }

            // 重绘界面
            myView.invalidate();
        };
        bn1.setOnClickListener(listener);
        bn2.setOnClickListener(listener);
        bn3.setOnClickListener(listener);
        bn4.setOnClickListener(listener);
        bn5.setOnClickListener(listener);
    }
}

layout/activity_main.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">

    <Button
        android:id="@+id/bn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/bitmap" />

    <Button
        android:id="@+id/bn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/line" />

    <Button
        android:id="@+id/bn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/radial" />

    <Button
        android:id="@+id/bn4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sweep" />

    <Button
        android:id="@+id/bn5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/composite" />


    <com.example.myapplication.MyView
        android:id="@+id/my_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

values/strings.xml

<resources>
    <string name="app_name">My Application</string>
    <string name="bitmap">位图</string>
    <string name="line">线性渐变</string>
    <string name="radial">圆形渐变</string>
    <string name="sweep">角度渐变</string>
    <string name="composite">混合</string>
</resources>
image.png

摘抄至《疯狂Android讲义(第4版)》

上一篇 下一篇

猜你喜欢

热点阅读