MatrixとCanvas

久々のpostです。MatrixとCanvasの動作に関してです。ちょっとチンプンカンプンなところがあったので動作を確認しながらまとめてみました

今回やりたいこと

  1. Matrix(とHandler)を使用してIconを縦方向に落下するAnimationをさせる
  2. Canvasを操作し文字列とIconを縦方向に描画する

Matrixを操作しIconを回転させ、画面に沿って落下させるの図


Canvasを操作し文字列、Iconを縦に回転させるの図

MatrixとCanvasで気付いたこと

1. Matrix

    • Matrixにはpre-concat系、post-concat系、set系のMatrix操作系methodがある
    • 引数に指定するx、yは画面に対してのx、yなのでmatrixを90度回転してもx軸、y軸は変わらない

2. Canvas

    • Canvas経由のmatrix操作はpre-concatなので、codeの並び順とは逆順に実行される
    • Canvasmatrix操作系methodで引数として指定するx、yは画面に対してのx、yなのでmatrixを90度回転してもx軸、y軸は変わらない
    • Canvas描画系methodで引数として指定するx、yはCanvasに対してのx、yなのでmatrixを90度回転するとx軸、y軸も合わせて回転する

その他気付いたこと

    • Viewを入れ替える際はView.INVISIBLEでは駄目でView.GONEを指定する必要がある
    • Matrixを使用する際にはscaleType属性にmatrixを指定する必要あり
    • Canvas#drawText()で指定する座標位置はtextのbaselineの座標位置

それでは実装です

layout xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
	android:id="@+id/button_1"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:text="@string/matrix" />
<Button
	android:id="@+id/button_2"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:text="@string/canvas" />
<!-- Matrixを使用する際にはscaleType属性にmatrixを指定する必要あり -->	
<ImageView
	android:id="@+id/image_1"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:scaleType="matrix"
	android:visibility="gone" />
<com.android.practice.matrix.MyImageView
	android:id="@+id/image_2"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:scaleType="matrix"
	android:visibility="gone" />
</LinearLayout>

Activity(Matrixを操作するclass)

package com.android.practice.matrix;

import android.app.Activity;
import android.graphics.Matrix;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MyMatrixPracticeActivity extends Activity {
    
	private static final String TAG = MyMatrixPracticeActivity.class.getSimpleName();
	private static boolean DEBUG = true;
	
	public static final int UPDATE_VIEW = 1;
	
	private ImageView mImage;
	private Button mButton1;
	private Button mButton2;
	
	private Handler mHandler = new Handler(){
		
		private Matrix mMtrx = new Matrix();
		private int mExtraTrans;
		
		@Override
		public void handleMessage(Message msg) {
			switch(msg.what){
			case UPDATE_VIEW:
				Log.d(TAG, "UPDATE_VIEW mExtraTrans:" + mExtraTrans);

				mMtrx.reset();
				
				if(mExtraTrans + getResources().getDrawable(R.drawable.icon).getIntrinsicWidth() + mImage.getPaddingBottom() < mImage.getHeight()){
					
					//引数に指定するx、yは画面に対してのx、yなのでmatrixを90度回転してもx軸、y軸は変わらない
					mMtrx.postTranslate(-mImage.getWidth()/2, -mImage.getHeight()/2);
					mMtrx.postRotate(90);
					mMtrx.postTranslate(-mImage.getHeight()/2 + getResources().getDrawable(R.drawable.icon).getIntrinsicHeight(), mImage.getWidth()/2);
					mMtrx.postTranslate(0, mExtraTrans);
					
					mImage.setImageMatrix(mMtrx);
					
					mExtraTrans += 5;
					
					Message newMsg = obtainMessage();
					newMsg.what = UPDATE_VIEW;
					sendMessage(newMsg);
				}
				break;
			default:
				super.handleMessage(msg);
			}
		}
	};;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setUpViews();
    }

	@Override
	protected void onResume() {
		super.onResume();
	}
	
	private void setUpViews(){
		mButton1 = (Button)findViewById(R.id.button_1);
		mButton1.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View arg0) {
				startAnimation1();
			}
		});
		mButton2 = (Button)findViewById(R.id.button_2);
		mButton2.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View arg0) {
				drawMyImageView();
			}
		});
	}
	
	private void startAnimation1(){
		if(mImage != null){
			//Viewを入れ替える際はView.INVISIBLEでは駄目でView.GONEを指定する必要がある
			mImage.setVisibility(View.GONE);
		}
		mImage = (ImageView)findViewById(R.id.image_1);
		mImage.setImageResource(R.drawable.icon);
		mImage.setVisibility(View.VISIBLE);
		
		if(DEBUG){
			Log.d(TAG, "startChangingImageView width: " + mImage.getWidth() + " height: " + mImage.getHeight());
			Log.d(TAG, "startChangingImageView measuredWidth: " + mImage.getMeasuredWidth() + " measuredHeight: " + mImage.getMeasuredHeight());
		}
		
		Message msg = mHandler.obtainMessage();
		msg.what = UPDATE_VIEW;
		mHandler.sendMessage(msg);
	}
	
	private void drawMyImageView(){
		mHandler.removeMessages(UPDATE_VIEW);
		if(mImage != null){
			//Viewを入れ替える際はView.INVISIBLEでは駄目でView.GONEを指定する必要がある
			mImage.setVisibility(View.GONE);
		}
		
		mImage = (ImageView)findViewById(R.id.image_2);
		mImage.setImageResource(R.drawable.icon);
		mImage.setVisibility(View.VISIBLE);
	}
}

ImageView拡張class(Canvasを操作)

package com.android.practice.matrix;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;

public class MyImageView extends ImageView {
	
	private static final String TAG = MyImageView.class.getSimpleName();
	private static final boolean DEBUG = true;
	
	private Paint mPaint;
	
	public MyImageView(Context context){
		super(context);
	}
	
	public MyImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setTextSize(40);
		mPaint.setARGB(255, 255, 0, 0);
	}

	public MyImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		if(DEBUG)Log.d(TAG, "onDraw Width:" + getWidth() + " Height: " + getHeight());
		canvas.save();
		
		//Canvas経由のmatrix操作はpreconcatなので、codeの並び順とは逆順に実行される
		//Canvasのmatrix操作系methodで引数として指定するx、yは画面に対してのx、yなのでmatrixを90度回転してもx軸、y軸は変わらない
		canvas.translate(-(getHeight() / 2 - getWidth() / 2), getWidth() / 2);
		canvas.rotate(90);
		canvas.translate(-getWidth() / 2, -getHeight() / 2);
		
		canvas.drawColor(Color.CYAN);
		//Canvasの描画系methodで引数として指定するx、yはCanvasに対してのx、yなのでmatrixを90度回転するとx軸、y軸も合わせて回転する
		//Canvas#drawText()で指定する座標位置はtextのbaselineの座標位置
		canvas.drawText("This is a text on (0, 0)", 0, 0, mPaint);
		canvas.drawLine(0, 0, getHeight(), 0, mPaint);
		canvas.drawText("This is a text on (0, -40)", 0, -40, mPaint);
		canvas.drawLine(0, -40, getHeight(), -40, mPaint);

		Drawable drawable = getDrawable();
		drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
		drawable.draw(canvas);
		
		canvas.restore();
	}
}

HashTag #Android, #Java, #Matrix, #Canvas, #Handler