Android中圖像和圖像處理

一Bitmap和BitmapFactory
這裏寫圖片描述
二Android繪圖基礎
這裏寫圖片描述
三Path類
Path
PathText
PathText
採用雙緩衝實現畫圖板
HandleDraw
彈球遊戲
彈球遊戲
matr
使用drawBitmapMesh扭曲圖像
Mesh
使用Shader填充圖形
這裏寫圖片描述

一 Bitmap和BitmapFactory

Bitmap提供下面靜態方法來創建Bitmap對象:
1. createBitmap(Bitmap source,int x ,int y,int width,int height).
2. createScaledBitmap(Bitmap src,int dstWidth,int dstHeight,boolean filter).
3. createBitmap(int width,int height,Bitmap.Config config)
4. createBitmap(Bitmap source,int x,int y,int width,int height,Matrix m,boolean filter)

BitmapFactory提供如下方法從不同的數據源來解析創建Bitmap對象。

  1. decodeByteArray(byte[] data,int offset,int length);
  2. decodeFile(String pathName);
  3. decodeFileDescriptor(FileDescriptor fd);
  4. decodeResource(Resource res,int id);
  5. decodeStream(inputStream is);

Android爲Bitmap提供下面兩個方法來判斷它是否回收,以及強制Bitmap回收自己。

  1. boolean isRecycled();
  2. void recycle();

代碼:

public class MainActivity extends Activity
{
    String[] images = null;
    AssetManager assets = null;
    int currentImg = 0;
    ImageView image;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        image = (ImageView) findViewById(R.id.image);
        try
        {
            assets = getAssets();
            // 獲取/assets/目錄下所有文件
            images = assets.list("");
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        // 獲取next按鈕
        final Button next = (Button) findViewById(R.id.next);
        // 爲next按鈕綁定事件監聽器,該監聽器將會查看下一張圖片
        next.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View sources)
            {
                // 如果發生數組越界
                if (currentImg >= images.length)
                {
                    currentImg = 0;
                }
                // 找到下一個圖片文件
                while (!images[currentImg].endsWith(".png")
                        && !images[currentImg].endsWith(".jpg")
                        && !images[currentImg].endsWith(".gif"))
                {
                    currentImg++;
                    // 如果已發生數組越界
                    if (currentImg >= images.length)
                    {
                        currentImg = 0;
                    }
                }
                InputStream assetFile = null;
                try
                {
                    // 打開指定資源對應的輸入流
                    assetFile = assets.open(images[currentImg++]);
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
                BitmapDrawable bitmapDrawable = (BitmapDrawable) image
                    .getDrawable();
                // 如果圖片還未回收,先強制回收該圖片
                if (bitmapDrawable != null
                        && !bitmapDrawable.getBitmap().isRecycled()) // ①
                {
                    bitmapDrawable.getBitmap().recycle();
                }
                // 改變ImageView顯示的圖片
                image.setImageBitmap(BitmapFactory
                    .decodeStream(assetFile)); // ②
            }
        });
    }
}

Android繪圖基礎,Canvas , Paint

方法 簡要說明
drawCircle() 畫圓
drawPath(Path path,Paint paint) 沿着指定Path繪製任意形狀
drawRect(RectF rect, Paint paint) 繪製區域,參數一爲RectF一個區域
drawPath(Path path, Paint paint) 繪製一個路徑,參數一爲Path路徑對象
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) 貼圖,參數一就是我們常規的Bitmap對象,參數二是源區域(這裏是bitmap),參數三是目標區域(應該在canvas的位置和大小),參數四是Paint畫刷對象,因爲用到了縮放和拉伸的可能,當原始Rect不等於目標Rect時性能將會有大幅損失。
drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) 畫線,參數一起始點的x軸位置,參數二起始點的y軸位置,參數三終點的x軸水平位置,參數四y軸垂直位置,最後一個參數爲Paint 畫刷對象。
drawPoint(float x, float y, Paint paint) 畫點,參數一水平x軸,參數二垂直y軸,第三個參數爲Paint對象。
drawText(String text, float x, floaty, Paint paint) 渲染文本,Canvas類除了上面的還可以描繪文字,參數一是String類型的文本,參數二x軸,參數三y軸,參數四是Paint對象。
drawOval(RectF oval, Paint paint) 畫橢圓,參數一是掃描區域,參數二爲paint對象;
drawCircle(float cx, float cy, float radius,Paint paint) 繪製圓,參數一是中心點的x軸,參數二是中心點的y軸,參數三是半徑,參數四是paint對象;
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 畫弧,參數一是RectF對象,一個矩形區域橢圓形的界限用於定義在形狀、大小、電弧,參數二是起始角(度)在電弧的開始,參數三掃描角(度)開始順時針測量的,參數四是如果這是真的話,包括橢圓中心的電弧,並關閉它,如果它是假這將是一個弧線,參數五是Paint對象;

還有如下方法進行座標變換

方法 簡要說明
rotate(float degrees,float px,float py) 對Canvas執行旋轉變換
scale(float sx,float sy,float px,float py) 對Canvas執行縮放變換
skew(float sx,float sy) 對Canvas執行傾斜變換
trenslate(float dx,float dy) 移動Canvas.向dx距離向下移動dy距離

代碼:

public class MyView extends View
{
    public MyView(Context context, AttributeSet set)
    {
        super(context, set);
    }
    @Override
    // 重寫該方法,進行繪圖
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        // 把整張畫布繪製成白色
        canvas.drawColor(Color.WHITE);
        Paint paint = new Paint();
        // 去鋸齒
        paint.setAntiAlias(true);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        int viewWidth = this.getWidth();
        // 繪製圓形
        canvas.drawCircle(viewWidth / 10 + 10, viewWidth / 10 + 10
            , viewWidth / 10, paint);
        // 繪製正方形
        canvas.drawRect(10 , viewWidth / 5 + 20 , viewWidth / 5 + 10
            , viewWidth * 2 / 5 + 20 , paint);
        // 繪製矩形
        canvas.drawRect(10, viewWidth * 2 / 5 + 30, viewWidth / 5 + 10
            , viewWidth / 2 + 30, paint);
        RectF re1 = new RectF(10, viewWidth / 2 + 40
            , 10 + viewWidth / 5 ,viewWidth * 3 / 5 + 40);
        // 繪製圓角矩形
        canvas.drawRoundRect(re1, 15, 15, paint);
        RectF re11 = new RectF(10, viewWidth * 3 / 5 + 50
            , 10 + viewWidth / 5 ,viewWidth * 7 / 10 + 50);
        // 繪製橢圓
        canvas.drawOval(re11, paint);
        // 定義一個Path對象,封閉成一個三角形
        Path path1 = new Path();
        path1.moveTo(10, viewWidth * 9 / 10 + 60);
        path1.lineTo(viewWidth / 5 + 10, viewWidth * 9 / 10 + 60);
        path1.lineTo(viewWidth / 10 + 10, viewWidth * 7 / 10 + 60);
        path1.close();
        // 根據Path進行繪製,繪製三角形
        canvas.drawPath(path1, paint);
        // 定義一個Path對象,封閉成一個五角形
        Path path2 = new Path();
        path2.moveTo(10 + viewWidth / 15, viewWidth * 9 / 10 + 70);
        path2.lineTo(10 + viewWidth * 2 / 15, viewWidth * 9 / 10 + 70);
        path2.lineTo(10 + viewWidth / 5, viewWidth + 70);
        path2.lineTo(10 + viewWidth / 10, viewWidth * 11/10 + 70);
        path2.lineTo(10 , viewWidth + 70);
        path2.close();
        // 根據Path進行繪製,繪製五角形
        canvas.drawPath(path2, paint);
        // ----------設置填充風格後繪製----------
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.RED);
        // 繪製圓形
        canvas.drawCircle(viewWidth * 3 / 10 + 20, viewWidth / 10 + 10
            , viewWidth / 10, paint);
        // 繪製正方形
        canvas.drawRect(viewWidth / 5 + 20 , viewWidth / 5 + 20
            , viewWidth * 2 / 5 + 20 , viewWidth * 2 / 5 + 20 , paint);
        // 繪製矩形
        canvas.drawRect(viewWidth / 5 + 20, viewWidth * 2 / 5 + 30
            , viewWidth * 2 / 5 + 20 , viewWidth / 2 + 30, paint);
        RectF re2 = new RectF(viewWidth / 5 + 20, viewWidth / 2 + 40
            , 20 + viewWidth * 2 / 5 ,viewWidth * 3 / 5 + 40);
        // 繪製圓角矩形
        canvas.drawRoundRect(re2, 15, 15, paint);
        RectF re21 = new RectF(20 + viewWidth / 5, viewWidth * 3 / 5 + 50
            , 20 + viewWidth * 2 / 5 ,viewWidth * 7 / 10 + 50);
        // 繪製橢圓
        canvas.drawOval(re21, paint);
        // 定義一個Path對象,封閉成一個三角形
        Path path3 = new Path();
        path3.moveTo(20 + viewWidth / 5, viewWidth * 9 / 10 + 60);
        path3.lineTo(viewWidth * 2 / 5 + 20, viewWidth * 9 / 10 + 60);
        path3.lineTo(viewWidth * 3 / 10 + 20, viewWidth * 7 / 10 + 60);
        path3.close();
        // 根據Path進行繪製,繪製三角形
        canvas.drawPath(path3, paint);
        // 定義一個Path對象,封閉成一個五角形
        Path path4 = new Path();
        path4.moveTo(20 + viewWidth *4 / 15, viewWidth * 9 / 10 + 70);
        path4.lineTo(20 + viewWidth / 3, viewWidth * 9 / 10 + 70);
        path4.lineTo(20 + viewWidth * 2 / 5, viewWidth + 70);
        path4.lineTo(20 + viewWidth * 3 / 10, viewWidth * 11/10 + 70);
        path4.lineTo(20 + viewWidth / 5 , viewWidth + 70);
        path4.close();
        // 根據Path進行繪製,繪製五角形
        canvas.drawPath(path4, paint);
        // ----------設置漸變器後繪製----------
        // 爲Paint設置漸變器
        Shader mShader = new LinearGradient(0, 0, 40, 60
            , new int[] {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW }
            , null , Shader.TileMode.REPEAT);
        paint.setShader(mShader);
        //設置陰影
        paint.setShadowLayer(25 , 20 , 20 , Color.GRAY);
        // 繪製圓形
        canvas.drawCircle(viewWidth / 2 + 30, viewWidth / 10 + 10
            , viewWidth / 10, paint);
        // 繪製正方形
        canvas.drawRect(viewWidth * 2 / 5 + 30 , viewWidth / 5 + 20
            , viewWidth * 3 / 5 + 30 , viewWidth * 2 / 5 + 20 , paint);
        // 繪製矩形
        canvas.drawRect(viewWidth * 2 / 5 + 30, viewWidth * 2 / 5 + 30
            , viewWidth * 3 / 5 + 30 , viewWidth / 2 + 30, paint);
        RectF re3 = new RectF(viewWidth * 2 / 5 + 30, viewWidth / 2 + 40
            , 30 + viewWidth * 3 / 5 ,viewWidth * 3 / 5 + 40);
        // 繪製圓角矩形
        canvas.drawRoundRect(re3, 15, 15, paint);
        RectF re31 = new RectF(30 + viewWidth *2 / 5, viewWidth * 3 / 5 + 50
            , 30 + viewWidth * 3 / 5 ,viewWidth * 7 / 10 + 50);
        // 繪製橢圓
        canvas.drawOval(re31, paint);
        // 定義一個Path對象,封閉成一個三角形
        Path path5 = new Path();
        path5.moveTo(30 + viewWidth * 2/ 5, viewWidth * 9 / 10 + 60);
        path5.lineTo(viewWidth * 3 / 5 + 30, viewWidth * 9 / 10 + 60);
        path5.lineTo(viewWidth / 2 + 30, viewWidth * 7 / 10 + 60);
        path5.close();
        // 根據Path進行繪製,繪製三角形
        canvas.drawPath(path5, paint);
        // 定義一個Path對象,封閉成一個五角形
        Path path6 = new Path();
        path6.moveTo(30 + viewWidth * 7 / 15, viewWidth * 9 / 10 + 70);
        path6.lineTo(30 + viewWidth * 8 / 15, viewWidth * 9 / 10 + 70);
        path6.lineTo(30 + viewWidth * 3 / 5, viewWidth + 70);
        path6.lineTo(30 + viewWidth / 2, viewWidth * 11/10 + 70);
        path6.lineTo(30 + viewWidth * 2 / 5 , viewWidth + 70);
        path6.close();
        // 根據Path進行繪製,繪製五角形
        canvas.drawPath(path6, paint);
        // ----------設置字符大小後繪製----------
        paint.setTextSize(48);
        paint.setShader(null);
        // 繪製7個字符串
        canvas.drawText(getResources().getString(R.string.circle)
            , 60 + viewWidth * 3 / 5, viewWidth / 10 + 10, paint);
        canvas.drawText(getResources().getString(R.string.square)
            , 60 + viewWidth * 3 / 5, viewWidth * 3 / 10 + 20, paint);
        canvas.drawText(getResources().getString(R.string.rect)
            , 60 + viewWidth * 3 / 5, viewWidth * 1 / 2 + 20, paint);
        canvas.drawText(getResources().getString(R.string.round_rect)
            , 60 + viewWidth * 3 / 5, viewWidth * 3 / 5 + 30, paint);
        canvas.drawText(getResources().getString(R.string.oval)
            , 60 + viewWidth * 3 / 5, viewWidth * 7 / 10 + 30, paint);
        canvas.drawText(getResources().getString(R.string.triangle)
            , 60 + viewWidth * 3 / 5, viewWidth * 9 / 10 + 30, paint);
        canvas.drawText(getResources().getString(R.string.pentagon)
            , 60 + viewWidth * 3 / 5, viewWidth * 11 / 10 + 30, paint);
    }
}

三Path類

Android還爲路徑繪製提供PathEffect來定義繪製效果。PathEffect還有如下子類:
1. ComposePathEffect
2. CornerPathEffect
3. DashPathEffect
4. DiscretePathEffect
5. PathDashPathEffect
6. SumPathEffect

代碼:

public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
    class MyView extends View
    {
        float phase;
        PathEffect[] effects = new PathEffect[7];
        int[] colors;
        private Paint paint;
        Path path;
        public MyView(Context context)
        {
            super(context);
            paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(4);
            // 創建並初始化Path
            path = new Path();
            path.moveTo(0, 0);
            for (int i = 1; i <= 40; i++)
            {
                // 生成40個點,隨機生成它們的Y座標,並將它們連成一條Path
                path.lineTo(i * 20, (float) Math.random() * 60);
            }
            // 初始化7個顏色
            colors = new int[] { Color.BLACK, Color.BLUE, Color.CYAN,
                Color.GREEN, Color.MAGENTA, Color.RED, Color.YELLOW };
        }
        @Override
        protected void onDraw(Canvas canvas)
        {
            // 將背景填充成白色
            canvas.drawColor(Color.WHITE);
            // -----------下面開始初始化7種路徑效果----------
            // 不使用路徑效果
            effects[0] = null;
            // 使用CornerPathEffect路徑效果
            effects[1] = new CornerPathEffect(10);
            // 初始化DiscretePathEffect
            effects[2] = new DiscretePathEffect(3.0f, 5.0f);
            // 初始化DashPathEffect
            effects[3] = new DashPathEffect(new float[] { 20, 10, 5, 10 },
                    phase);
            // 初始化PathDashPathEffect
            Path p = new Path();
            p.addRect(0, 0, 8, 8, Path.Direction.CCW);
            effects[4] = new PathDashPathEffect(p, 12, phase,
                    PathDashPathEffect.Style.ROTATE);
            // 初始化ComposePathEffect
            effects[5] = new ComposePathEffect(effects[2], effects[4]);
            effects[6] = new SumPathEffect(effects[4], effects[3]);
            // 將畫布移動到(8、8)處開始繪製
            canvas.translate(8, 8);
            // 依次使用7種不同路徑效果、7種不同的顏色來繪製路徑
            for (int i = 0; i < effects.length; i++)
            {
                paint.setPathEffect(effects[i]);
                paint.setColor(colors[i]);
                canvas.drawPath(path, paint);
                canvas.translate(0, 60);
            }
            // 改變phase值,形成動畫效果
            phase += 1;
            invalidate();
        }
    }
}

PathText

代碼:

public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(new TextView(this));
    }
    class TextView extends View
    {
        final String DRAW_STR = "aserbao";
        Path[] paths = new Path[3];
        Paint paint;
        public TextView(Context context)
        {
            super(context);
            paths[0] = new Path();
            paths[0].moveTo(0, 0);
            for (int i = 1; i <= 20; i++)
            {
                // 生成20個點,隨機生成它們的Y座標,並將它們連成一條Path
                paths[0].lineTo(i * 30, (float) Math.random() * 30);
            }
            paths[1] = new Path();
            RectF rectF = new RectF(0, 0, 600, 360);
            paths[1].addOval(rectF, Path.Direction.CCW);
            paths[2] = new Path();
            paths[2].addArc(rectF, 60, 180);
            // 初始化畫筆
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(Color.CYAN);
            paint.setStrokeWidth(1);
        }
        @Override
        protected void onDraw(Canvas canvas)
        {
            canvas.drawColor(Color.WHITE);
            canvas.translate(40, 40);
            // 設置從右邊開始繪製(右對齊)
            paint.setTextAlign(Paint.Align.RIGHT);
            paint.setTextSize(20);
            // 繪製路徑
            paint.setStyle(Paint.Style.STROKE);
            canvas.drawPath(paths[0], paint);
            paint.setTextSize(40);
            // 沿着路徑繪製一段文本
            paint.setStyle(Paint.Style.FILL);
            canvas.drawTextOnPath(DRAW_STR, paths[0], -8, 20, paint);
            // 對Canvas進行座標變換:畫布下移60
            canvas.translate(0, 60);
            // 繪製路徑
            paint.setStyle(Paint.Style.STROKE);
            canvas.drawPath(paths[1], paint);
            // 沿着路徑繪製一段文本
            paint.setStyle(Paint.Style.FILL);
            canvas.drawTextOnPath(DRAW_STR, paths[1], -20, 20, paint);
            // 對Canvas進行座標變換: 畫布下移360
            canvas.translate(0, 360);
            // 繪製路徑
            paint.setStyle(Paint.Style.STROKE);
            canvas.drawPath(paths[2], paint);
            // 沿着路徑繪製一段文本
            paint.setStyle(Paint.Style.FILL);
            canvas.drawTextOnPath(DRAW_STR, paths[2], -10, 20, paint);
        }
    }
}

採用雙緩衝實現畫圖板

DrawView代碼:

public class DrawView extends View
{
    // 定義記錄前一個拖動事件發生點的座標
    float preX;
    float preY;
    private Path path;
    public Paint paint = null;
    // 定義一個內存中的圖片,該圖片將作爲緩衝區
    Bitmap cacheBitmap = null;
    // 定義cacheBitmap上的Canvas對象
    Canvas cacheCanvas = null;
    public DrawView(Context context, int width , int height)
    {
        super(context);
        // 創建一個與該View相同大小的緩存區
        cacheBitmap = Bitmap.createBitmap(width, height,
            Bitmap.Config.ARGB_8888);
        cacheCanvas = new Canvas();
        path = new Path();
        // 設置cacheCanvas將會繪製到內存中的cacheBitmap上
        cacheCanvas.setBitmap(cacheBitmap);
        // 設置畫筆的顏色
        paint = new Paint(Paint.DITHER_FLAG);
        paint.setColor(Color.RED);
        // 設置畫筆風格
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);
        // 反鋸齒
        paint.setAntiAlias(true);
        paint.setDither(true);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        // 獲取拖動事件的發生位置
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                // 從前一個點繪製到當前點之後,把當前點定義成下次繪製的前一個點
                path.moveTo(x, y);
                preX = x;
                preY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 從前一個點繪製到當前點之後,把當前點定義成下次繪製的前一個點
                path.quadTo(preX, preY, x, y);
                preX = x;
                preY = y;
                break;
            case MotionEvent.ACTION_UP:
                cacheCanvas.drawPath(path, paint); // ①
                path.reset();
                break;
        }
        invalidate();
        // 返回true表明處理方法已經處理該事件
        return true;
    }
    @Override
    public void onDraw(Canvas canvas)
    {
        Paint bmpPaint = new Paint();
        // 將cacheBitmap繪製到該View組件上
        canvas.drawBitmap(cacheBitmap, 0, 0, bmpPaint); // ②
        // 沿着path繪製
        canvas.drawPath(path, paint);
    }
}

MainActivity代碼

public class MainActivity extends Activity
{
    EmbossMaskFilter emboss;
    BlurMaskFilter blur;
    DrawView drawView;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        LinearLayout line = new LinearLayout(this);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        // 獲取創建的寬度和高度
        getWindowManager().getDefaultDisplay()
            .getRealMetrics(displayMetrics);
        // 創建一個DrawView,該DrawView的寬度、高度與該Activity保持相同
        drawView = new DrawView(this, displayMetrics.widthPixels
            , displayMetrics.heightPixels);
        line.addView(drawView);
        setContentView(line);
        emboss = new EmbossMaskFilter(new float[]
            { 1.5f, 1.5f, 1.5f }, 0.6f, 6, 4.2f);
        blur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
    }
    @Override
    // 負責創建選項菜單
    public boolean onCreateOptionsMenu(Menu menu)
    {
        MenuInflater inflator = new MenuInflater(this);
        // 裝載R.menu.my_menu對應的菜單,並添加到menu中
        inflator.inflate(R.menu.menu_main, menu);
        return super.onCreateOptionsMenu(menu);
    }
    @Override
    // 菜單項被單擊後的回調方法
    public boolean onOptionsItemSelected(MenuItem mi)
    {
        // 判斷單擊的是哪個菜單項,並有針對性地作出響應
        switch (mi.getItemId())
        {
            case R.id.red:
                drawView.paint.setColor(Color.RED);
                mi.setChecked(true);
                break;
            case R.id.green:
                drawView.paint.setColor(Color.GREEN);
                mi.setChecked(true);
                break;
            case R.id.blue:
                drawView.paint.setColor(Color.BLUE);
                mi.setChecked(true);
                break;
            case R.id.width_1:
                drawView.paint.setStrokeWidth(1);
                break;
            case R.id.width_3:
                drawView.paint.setStrokeWidth(3);
                break;
            case R.id.width_5:
                drawView.paint.setStrokeWidth(5);
                break;
            case R.id.blur:
                drawView.paint.setMaskFilter(blur);
                break;
            case R.id.emboss:
                drawView.paint.setMaskFilter(emboss);
                break;
        }
        return true;
    }
}

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:title="@string/color">
        <menu>
            <!-- 定義一組單選菜單項 -->
            <group android:checkableBehavior="single">
                <!-- 定義多個菜單項 -->
                <item android:id="@+id/red"
                     android:title="@string/color_red"/>
                <item android:id="@+id/green"
                     android:title="@string/color_green"/>
                <item android:id="@+id/blue"
                     android:title="@string/color_blue"/>
            </group>
        </menu>
    </item>
    <item android:title="@string/width">
        <menu>
            <!-- 定義一組菜單項 -->
            <group>
                <!-- 定義三個菜單項 -->
                <item android:id="@+id/width_1"
                     android:title="@string/width_1"/>
                <item android:id="@+id/width_3"
                     android:title="@string/width_3"/>
                <item android:id="@+id/width_5"
                     android:title="@string/width_5"/>
            </group>
        </menu>
    </item>
    <item android:id="@+id/blur" android:title="@string/blur"/>
    <item android:id="@+id/emboss" android:title="@string/emboss"/>
</menu>

彈球遊戲

代碼:

public class MainActivity extends Activity
{
    // 桌面的寬度
    private int tableWidth;
    // 桌面的高度
    private int tableHeight;
    // 球拍的垂直位置
    private int racketY;
    // 下面定義球拍的高度和寬度
    private final int RACKET_HEIGHT = 30;
    private final int RACKET_WIDTH = 90;
    // 小球的大小
    private final int BALL_SIZE = 16;
    // 小球縱向的運行速度
    private int ySpeed = 15;
    Random rand = new Random();
    // 返回一個-0.5~0.5的比率,用於控制小球的運行方向
    private double xyRate = rand.nextDouble() - 0.5;
    // 小球橫向的運行速度
    private int xSpeed = (int) (ySpeed * xyRate * 2);
    // ballX和ballY代表小球的座標
    private int ballX = rand.nextInt(200) + 20;
    private int ballY = rand.nextInt(10) + 20;
    // racketX代表球拍的水平位置
    private int racketX = rand.nextInt(200);
    // 遊戲是否結束的旗標
    private boolean isLose = false;
    private GameView contentView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 去掉窗口標題
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 全屏顯示
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
        // 創建GameView組件
        final GameView gameView = new GameView(this);
        setContentView(gameView);
        // 獲取窗口管理器
        WindowManager windowManager = getWindowManager();
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        // 獲得屏幕寬和高
        tableWidth = metrics.widthPixels;
        tableHeight = metrics.heightPixels;
        racketY = tableHeight - 80;
        final Handler handler = new Handler() {
            public void handleMessage(Message msg) {
                if (msg.what == 0x123) {
                    gameView.invalidate();
                }
            }
        };
        gameView.setOnKeyListener(new OnKeyListener() // ②
        {
            @Override
            public boolean onKey(View source, int keyCode, KeyEvent event) {
                // 獲取由哪個鍵觸發的事件
                switch (event.getKeyCode()) {
                    // 控制擋板左移
                    case KeyEvent.KEYCODE_A:
                        if (racketX > 0) racketX -= 10;
                        break;
                    // 控制擋板右移
                    case KeyEvent.KEYCODE_D:
                        if (racketX < tableWidth - RACKET_WIDTH) racketX += 10;
                        break;
                }
                // 通知gameView組件重繪
                gameView.invalidate();
                return true;
            }
        });
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() // ①
        {
            @Override
            public void run() {
                // 如果小球碰到左邊邊框
                if (ballX <= 0 || ballX >= tableWidth - BALL_SIZE) {
                    xSpeed = -xSpeed;
                }
                // 如果小球高度超出了球拍位置,且橫向不在球拍範圍之內,遊戲結束
                if (ballY >= racketY - BALL_SIZE
                        && (ballX < racketX || ballX > racketX
                        + RACKET_WIDTH)) {
                    timer.cancel();
                    // 設置遊戲是否結束的旗標爲true
                    isLose = true;
                }
                // 如果小球位於球拍之內,且到達球拍位置,小球反彈
                else if (ballY <= 0
                        || (ballY >= racketY - BALL_SIZE
                        && ballX > racketX && ballX <= racketX
                        + RACKET_WIDTH)) {
                    ySpeed = -ySpeed;
                }
                // 小球座標增加
                ballY += ySpeed;
                ballX += xSpeed;
                // 發送消息,通知系統重繪組件
                handler.sendEmptyMessage(0x123);
            }
        }, 0, 100);
    }
    class GameView extends View
    {
        Paint paint = new Paint();
        public GameView(Context context)
        {
            super(context);
            setFocusable(true);
        }
        // 重寫View的onDraw方法,實現繪畫
        public void onDraw(Canvas canvas)
        {
            paint.setStyle(Paint.Style.FILL);
            // 設置去鋸齒
            paint.setAntiAlias(true);
            // 如果遊戲已經結束
            if (isLose)
            {
                paint.setColor(Color.RED);
                paint.setTextSize(40);
                canvas.drawText("遊戲已結束", tableWidth / 2 - 100, 200, paint);
            }
            // 如果遊戲還未結束
            else
            {
                // 設置顏色,並繪製小球
                paint.setColor(Color.rgb(255, 0, 0));
                canvas.drawCircle(ballX, ballY, BALL_SIZE, paint);
                // 設置顏色,並繪製球拍
                paint.setColor(Color.rgb(80, 80, 200));
                canvas.drawRect(racketX, racketY, racketX + RACKET_WIDTH,
                        racketY + RACKET_HEIGHT, paint);
            }
        }
    }
}

圖形特效處理

Matrix控制變換步驟:

    1. 獲取Matrix對象。
    2. 調用Matrix的平移,旋轉,縮放,傾斜等。
    3. 將程序對Matrix所做的變換應用的指定圖形或組件。

代碼:

public class MyView extends View
{
    // 初始的圖片資源
    private Bitmap bitmap;
    // Matrix 實例
    private Matrix matrix = new Matrix();
    // 設置傾斜度
    private float sx = 0.0f;
    // 位圖寬和高
    private int width, height;
    // 縮放比例
    private float scale = 1.0f;
    // 判斷縮放還是旋轉
    private boolean isScale = false;
    public MyView(Context context , AttributeSet set)
    {
        super(context , set);
        // 獲得位圖
        bitmap = ((BitmapDrawable) context.getResources().getDrawable(
            R.drawable.a)).getBitmap();
        // 獲得位圖寬
        width = bitmap.getWidth();
        // 獲得位圖高
        height = bitmap.getHeight();
        // 使當前視圖獲得焦點
        this.setFocusable(true);
    }
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        // 重置Matrix
        matrix.reset();
        if (!isScale)
        {
            // 旋轉Matrix
            matrix.setSkew(sx, 0);
        }
        else
        {
            // 縮放Matrix
            matrix.setScale(scale, scale);
        }
        // 根據原始位圖和Matrix創建新圖片
        Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, width, height,
                matrix, true);
        // 繪製新位圖
        canvas.drawBitmap(bitmap2, matrix, null);
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
        switch(keyCode)
        {
            // 向左傾斜
            case KeyEvent.KEYCODE_A:
                isScale = false;
                sx += 0.1;
                postInvalidate();
                break;
            // 向右傾斜
            case KeyEvent.KEYCODE_D:
                isScale = false;
                sx -= 0.1;
                postInvalidate();
                break;
            // 放大
            case KeyEvent.KEYCODE_W:
                isScale = true;
                if (scale < 2.0)
                    scale += 0.1;
                postInvalidate();
                break;
            // 縮小
            case KeyEvent.KEYCODE_S:
                isScale = true;
                if (scale > 0.5)
                    scale -= 0.1;
                postInvalidate();
                break;
        }
        return super.onKeyDown(keyCode, event);
    }
}

使用drawBitmapMesh扭曲圖像

可以使用此方法在Android應用中開發除“水波盪漾”“風吹旗幟”等扭曲效果

代碼:

public class MainActivity extends Activity
{
    private Bitmap bitmap;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this, R.drawable.jinta));
    }
    private class MyView extends View
    {
        // 定義兩個常量,這兩個常量指定該圖片橫向、縱向上都被劃分爲20格
        private final int WIDTH = 20;
        private final int HEIGHT = 20;
        // 記錄該圖片上包含441個頂點
        private final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
        // 定義一個數組,保存Bitmap上的21 * 21個點的座標
        private final float[] verts = new float[COUNT * 2];
        // 定義一個數組,記錄Bitmap上的21 * 21個點經過扭曲後的座標
        // 對圖片進行扭曲的關鍵就是修改該數組裏元素的值
        private final float[] orig = new float[COUNT * 2];
        public 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;
            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座標
                    orig[index * 2 + 0] = verts[index * 2 + 0] = fx;
                    orig[index * 2 + 1] = verts[index * 2 + 1] = fy;
                    index += 1;
                }
            }
            // 設置背景色
            setBackgroundColor(Color.WHITE);
        }
        @Override
        protected 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 + 0];
                float dy = cy - orig[i + 1];
                float dd = dx * dx + dy * dy;
                // 計算每個座標點與當前點(cx、cy)之間的距離
                float d = (float) Math.sqrt(dd);
                // 計算扭曲度,距離當前點(cx、cy)越遠,扭曲度越小
                float pull = 100000 / ((float) (dd * d));
                // 對verts數組(保存bitmap上21 * 21個點經過扭曲後的座標)重新賦值
                if (pull >= 1)
                {
                    verts[i + 0] = cx;
                    verts[i + 1] = cy;
                }
                else
                {
                    // 控制各頂點向觸摸事件發生點偏移
                    verts[i + 0] = orig[i + 0] + 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;
        }
    }
}

使用Shader填充圖形

MainActivity:

public class MainActivity extends Activity
        implements OnClickListener
{
    // 聲明位圖渲染對象
    private Shader[] shaders = new Shader[5];
    // 聲明顏色數組
    private int[] colors;
    MyView myView;
    // 自定義視圖類
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        myView = (MyView)findViewById(R.id.my_view);
        // 獲得Bitmap實例
        Bitmap bm = BitmapFactory.decodeResource(getResources()
                , R.drawable.water);
        // 設置漸變的顏色組,也就是按紅、綠、藍的方式漸變
        colors = new int[] { Color.BLACK, Color.WHITE, Color.GRAY };
        // 實例化BitmapShader,x座標方向重複圖形,y座標方向鏡像圖形
        shaders[0] = new BitmapShader(bm, TileMode.REPEAT,
                TileMode.MIRROR);
        // 實例化LinearGradient
        shaders[1] = new LinearGradient(0, 0, 100, 100
                , colors, null, TileMode.REPEAT);
        // 實例化RadialGradient
        shaders[2] = new RadialGradient(100, 100, 80, colors, null,
                TileMode.REPEAT);
        // 實例化SweepGradient
        shaders[3] = new SweepGradient(160, 160, colors, null);
        // 實例化ComposeShader
        shaders[4] = new ComposeShader(shaders[1], shaders[2],
                PorterDuff.Mode.DARKEN);
        Button bn1 = (Button)findViewById(R.id.bn1);
        Button bn2 = (Button)findViewById(R.id.bn2);
        Button bn3 = (Button)findViewById(R.id.bn3);
        Button bn4 = (Button)findViewById(R.id.bn4);
        Button bn5 = (Button)findViewById(R.id.bn5);
        bn1.setOnClickListener(this);
        bn2.setOnClickListener(this);
        bn3.setOnClickListener(this);
        bn4.setOnClickListener(this);
        bn5.setOnClickListener(this);
    }
    @Override
    public void onClick(View source)
    {
        switch(source.getId())
        {
            case R.id.bn1:
                myView.paint.setShader(shaders[0]);
                break;
            case R.id.bn2:
                myView.paint.setShader(shaders[1]);
                break;
            case R.id.bn3:
                myView.paint.setShader(shaders[2]);
                break;
            case R.id.bn4:
                myView.paint.setShader(shaders[3]);
                break;
            case R.id.bn5:
                myView.paint.setShader(shaders[4]);
                break;
        }
        // 重繪界面
        myView.invalidate();
    }
}

MyView代碼:

public class MyView extends View
{
    // 聲明畫筆
    public Paint paint;
    public MyView(Context context , AttributeSet set)
    {
        super(context , set);
        paint = new Paint();
        paint.setColor(Color.RED);
    }
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        // 使用指定Paint對象畫矩形
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
    }
}
發佈了50 篇原創文章 · 獲贊 169 · 訪問量 52萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章