@blog.justoneplanet.info

日々勉強

Kindle Fire HDでcanvas.drawPictureするとUnsupportedOperationExceptionでクラッシュする

以下の部分でUnsupportedOperationExceptionが発生しクラッシュする。

canvas.drawPicture(picture);

drawPicture()はハードウェアアクセラレーションに対応していないらしいので以下のように修正した。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && canvas.isHardwareAccelerated()) {
  picture.draw(canvas);
}
else {
  canvas.drawPicture(picture);
}

追記

ImageViewなどのViewの描画でdrawableのdrawの中で以下の処理がされるとkindleでは描画できない。

picture.draw(canvas);

以下のようにハードウェアアクセラレーションが有効になっているViewでソフトウェアレンダリングするように指定した。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {// for kindle
  imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

以下のようにPictureDrawableをBitmapに変換してViewにセットしても上手く表示できる。

  canvas.drawBitmap(pictureDrawable2Bitmap(picture), new Matrix(), new Paint());
private static Bitmap pictureDrawable2Bitmap(Picture picture){
  PictureDrawable pictureDrawable = new PictureDrawable(picture);
  Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(),pictureDrawable.getIntrinsicHeight(), Config.ARGB_8888);
  Canvas canvas = new Canvas(bitmap);
  canvas.drawPicture(pictureDrawable.getPicture());
  return bitmap;
}

今回はdrawが頻繁にコールされるのでこの手法は取らなかった。

AndroidでViewがResizeされたのを検知する

殆どstackoverflowのコードと同様だがlistenerに発火する条件だけ変更した。

public class DetectableResizedLinearLayout extends LinearLayout {
    private OnResizedListener mListener;

    public DetectableResizedLinearLayout(Context context) {
        super(context);
    }
    public DetectableResizedLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public DetectableResizedLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    
    public void setOnSoftKeyShownListener(OnResizedListener listener) {
        mListener = listener;
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mListener != null) {
            final int newSpec = MeasureSpec.getSize(heightMeasureSpec); 
            final int oldSpec = getMeasuredHeight();
            if (oldSpec > newSpec + oldSpec / 4){
                mListener.onContracted();
            }
            else if (oldSpec + newSpec / 4 < newSpec) {
                mListener.onExpanded();
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    public interface OnResizedListener {
        public void onContracted();
        public void onExpanded();
    }
}

上述のようにしないと非同期で画像を読み込むViewなどがあった場合など想定していないタイミングで発火してしまう。

参考

androidでレンダリング後のViewのサイズを取得する

onClickなどのユーザイベント実行時に取得するならば以下の方法で取得ができる。

mView.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        int width = v.getWidth();
        int height = v.getHeight();
    }
});

これを利用して以下のようにすると正しく取得できない。

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    int width = mView.getWidth();// 0
    int height = mView.getHeight();// 0
}

onCreate, onStart, onResumeではレンダリングが終了していないためサイズが0となる。

■解決策

以下のようにViewTreeObserverを利用することでレンダリング後のサイズを取得することができる。

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    ViewTreeObserver viewTreeObserver = mView.getViewTreeObserver();
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            int width = mView.getWidth();
            int height = mView.getHeight();
        }
    });
}

androidでdraggableなviewを作る

■実装

DraggableView.java

package info.justoneplanet.android.sample.drag;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class DraggableView extends View {
    @SuppressWarnings("unused")
    private static final String TAG = DraggableView.class.getSimpleName();
    private final DraggableView self = this;
    private int offsetX;
    private int offsetY;
    private int currentX;
    private int currentY;
    
    public DraggableView(Context context) {
        super(context);
    }
    public DraggableView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public DraggableView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getRawX();// touch eventが発生したx座標
        int y = (int) event.getRawY();// touch eventが発生したy座標
        
        /* (2) 1つ前の値であるoffsetから
         * 現在ドラッグしている座標を引いて差を出し
         * 現在の座標に加算する
         * 最後にoffset値を更新する
         */
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            Log.d("ACTION_MOVE", "ACTION_MOVE");
            int diffX = x - offsetX;
            int diffY = y - offsetY;
            currentX = currentX + diffX;
            currentY = currentY + diffY;
            layout(currentX, currentY, currentX + getWidth(), currentY + getHeight());
            offsetX = x;
            offsetY = y;
        }
        //...(1) タッチした時の値を初期値にする
        else if (event.getAction() == MotionEvent.ACTION_DOWN) {
            Log.d("ACTION_DOWN", "ACTION_DOWN");
            offsetX = x;
            offsetY = y;
        }
        else if (event.getAction() == MotionEvent.ACTION_UP) {
            Log.d("ACTION_UP", "ACTION_UP");
        }
        return true;
    }
}

ACTION_MOVEの方が実行回数が大きいので先に記述する。

main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <info.justoneplanet.android.sample.drag.DraggableView
        android:layout_width="100dip"
        android:layout_height="100dip"
        android:background="#ff00ff00">
    </info.justoneplanet.android.sample.drag.DraggableView>
</FrameLayout>

MainActivity.javaは特に何も変更していない。Viewなので継承して使える。ImageViewとかでも同じように実装できる。

androidのView

めも。サンプルコードなのでアレ。

TextView

テキストを表示する場合は以下のようにする。

<TextView
    android:id="@+id/list_face"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="17sp"
    android:textColor="#333333" />

EditText

以下のようにして編集可能なテキスト領域を設置する。

<EditText
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="top"
    android:id="@+id/edit_text"/>

デフォルトでは水平方向でセンタリングされるのでgravityを指定し変更する。

AutoCompleteTextView

以下のように入力補助付きのテキストエリアを生成できる。

<AutoCompleteTextView
    android:id="@+id/autocomp"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:completionThreshold="2" />

但し、これだけでは完結しない。

候補表示用レイアウトファイル

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
</TextView>

プログラム

以下のようにして入力候補をセットする。

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.text, new String[]{"hoge", "fuga", "piyo"});
((AutoCompleteTextView) findViewById(R.id.autocomp)).setAdapter(adapter);

ImageView

画像表示用View。

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_launcher"
    android:id="@+id/icon" />

VideoView

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:id="@+id/movie" />

以下のようにして動画ソースをセットする。

((VideoView) findViewById(R.id.movie)).setVideoPath("/data/adult.mp4");

Button

以下のようにしてボタンを設置することができる。

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="@string/app_name" />

以下のようにしてクリックした時のイベントを定義する。

((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
});

androidでradioボタンを使う

■レイアウトファイル

以下のようにすることで画面にラジオボタンを配置できる。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <RadioGroup
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:id="@+id/RadioGroup">
        <RadioButton
            android:text="1"
            android:id="@+id/btn1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <RadioButton
            android:text="2"
            android:id="@+id/btn2"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <RadioButton
            android:text="3"
            android:id="@+id/btn3"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        </RadioGroup>
</LinearLayout>

■状態の取得

殆どの場合はコードからラジオボタンの状態を検知したいはずだ。

package info.justoneplanet.android.sample.radiobutton;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;

public class RadioButtonActivity extends Activity implements OnCheckedChangeListener {
    private RadioGroup mRadioGroup;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mRadioGroup = (RadioGroup) findViewById(R.id.RadioGroup);
        mRadioGroup.setOnCheckedChangeListener(this);
    }
    
    // チェックの状態が変更されるごとに発火する
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        if (-1 == checkedId) {
            // 何もチェックされていないとき
        }
        else {
            // チェックされているとき
            Log.e("checked", getResources().getResourceEntryName(checkedId));// viewのID名を表示
        }
    }
    
    // 保存ボタンなどで取得したい場合は以下のメソッドを利用する
    @Override
    public void onPause() {
        mRadioGroup.getCheckedRadioButtonId();
    }
}