monaka android memo

Androidの勉強中!調べたことを忘れないためのブログです!

setBackground でエラー発生。

Android4.2.2でアプリ開発していて、そのアプリをAndroid2.3.6?で動かした時に発生しました。

若干落ち込みながら、落ちたところを探すと、Viewに対して「setBackground()」で、背景画像を差し替えている所で落ちていました。

「setBackground()」について調べると、API LEVEL 16から導入されたよーということでした。
View | Android Developers


API LEVEL 16で導入されたものを、Android2.3(API LEVEL 10)で実行したので、落ちるのも当然ですね(´・ω・`)

API LEVEL 16以上でなければ、「setBackgroundDrawable()」を使用するように修正すると下記のような感じになりました。

/**
 * バックグラウンドの画像を差し替える。
 * AndroidOSのVerによって使用できるAPIが変わるため専用の関数を作成する。
 * @param v View
 * @param d 画像
 */
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
public void setBackground(View v, Drawable d)
{
	int sdk = android.os.Build.VERSION.SDK_INT;

	if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN)
	{
		//API LEVEL 16以下の時。
		v.setBackgroundDrawable(d);
	}
	else
	{
		//API LEVEL 16以上の時。
		v.setBackground(d);
	}
}


Viewの背景を差し替える方法は他にもあるので、ベストな修正とは思いませんが、
AndroidのVerによって処理を変えるということを、あまりやったことがなかったのでメモします( ・`ω・´)

TextViewのフォントサイズを自動調整する。

限られたTextViewの中に、もし大きいフォントサイズを指定してしまった時に、
Viewに収まるようにフォントサイズを自動調整できないかなーと考えたので、実際にやってみたことをメモします。

まず横幅ですが、下記のURLを参考にしました。
https://gist.github.com/STAR-ZERO/2934490

次に、縦幅については調べてみても見つからなかったので、縦幅と横幅を自動リサイズするクラスを作成しました。

import android.content.Context;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * フォントサイズ自動調整TextView
 */
public class ResizeTextView extends TextView
{
	/**
	 * コンストラクタ
	 * @param context
	 */
	public ResizeTextView(Context context)
	{
		super(context);
	}

	/**
	 * コンストラクタ
	 * @param context
	 * @param attrs
	 */
	public ResizeTextView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
	}

	/**
	 * 子Viewの位置を決める
	 */
	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom)
	{
		super.onLayout(changed, left, top, right, bottom);
		resize();
	}

	/**
	 * テキストサイズ調整
	 */
	private void resize()
	{
		/** 最小のテキストサイズ */
		final float MIN_TEXT_SIZE = 10f;

		int viewHeight = this.getHeight();	// Viewの縦幅
		int viewWidth = this.getWidth();	// Viewの横幅

		// テキストサイズ
		float textSize = getTextSize();

		// Paintにテキストサイズ設定
		Paint paint = new Paint();
		paint.setTextSize(textSize);

		// テキストの縦幅取得
		FontMetrics fm = paint.getFontMetrics();
		float textHeight = (float) (Math.abs(fm.top)) + (Math.abs(fm.descent));

		// テキストの横幅取得
		float textWidth = paint.measureText(this.getText().toString());

		// 縦幅と、横幅が収まるまでループ
		while (viewHeight < textHeight | viewWidth < textWidth)
		{
			// 調整しているテキストサイズが、定義している最小サイズ以下か。
			if (MIN_TEXT_SIZE >= textSize)
			{
				// 最小サイズ以下になる場合は最小サイズ
				textSize = MIN_TEXT_SIZE;
				break;
			}

			// テキストサイズをデクリメント
			textSize--;

			// Paintにテキストサイズ設定
			paint.setTextSize(textSize);

			// テキストの縦幅を再取得
			fm = paint.getFontMetrics();
			textHeight = (float) (Math.abs(fm.top)) + (Math.abs(fm.descent));

			// テキストの横幅を再取得
			textWidth = paint.measureText(this.getText().toString());
		}

		// テキストサイズ設定
		setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
	}
}


簡単に説明すると、TextViewを継承したクラスで、onLayout()でフォントサイズを調整するresize()を呼んでます。
下記にresize()について説明していきます。


1.まずTextViewの縦幅と横幅を取得します。

int viewHeight = this.getHeight();	// Viewの縦幅
int viewWidth = this.getWidth();	// Viewの横幅


2.TextViewに設定されているフォントサイズを取得し、Paintクラスにフォントサイズをセットします。

// テキストサイズ
float textSize = getTextSize();

// Paintにテキストサイズ設定
Paint paint = new Paint();
paint.setTextSize(textSize);


3.テキストの縦幅、横幅を取得します。
FontMetricsと、Paintを使用することで、実際にテキストが表示される時のサイズを取得することができます。

// テキストの縦幅取得
FontMetrics fm = paint.getFontMetrics();
float textHeight = (float) (Math.abs(fm.top)) + (Math.abs(fm.descent));

// テキストの横幅取得
float textWidth = paint.measureText(this.getText().toString());	


4.TextViewに収まるまでテキストサイズをデクリメントしていきます。

// 縦幅と、横幅が収まるまでループ
while (viewHeight < textHeight | viewWidth < textWidth)
{
	// 調整しているテキストサイズが、定義している最小サイズ以下か。
	if (MIN_TEXT_SIZE >= textSize)
	{
		// 最小サイズ以下になる場合は最小サイズ
		textSize = MIN_TEXT_SIZE;
		break;
	}

	// テキストサイズをデクリメント
	textSize--;

	// Paintにテキストサイズ設定
	paint.setTextSize(textSize);

	// テキストの縦幅を再取得
	fm = paint.getFontMetrics();
	textHeight = (float) (Math.abs(fm.top)) + (Math.abs(fm.descent));

	// テキストの横幅を再取得
	textWidth = paint.measureText(this.getText().toString());
}


5.最後に調整したテキストサイズを、TextViewにセットします。

// テキストサイズ設定
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);

これを下記のXMLで実行してみます。
上のTextViewはいつものやつで、下のが今回のクラスです。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:text="AaYy"
        android:textSize="200dp"
        android:background="#ffff66"
        android:bufferType="spannable" />

    <com.example.resizetextview.ResizeTextView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:text="AaYy"
        android:textSize="200dp"
        android:background="#ccff99" 
        android:bufferType="spannable"/>

</LinearLayout>

f:id:monakapro:20130802130021p:plain

いつものTextViewに対し、大きいフォントサイズを指定すると当然はみ出てしまいますが、
今回のクラスではフォントサイズを調整して表示されています。


XMLのTextViewに追加している下記ですが、

android:bufferType="spannable"

これをいれないと、テキストのベースラインが変更されず、
Viewの横幅だけ小さくなっていき、縦幅は大きいフォントサイズの時のままになることがあったからです(´・ω・`)



ここから余談です!
View継承したクラスで、Canvas使ってテキスト書いてみたけど、かなり重かったのでやめました。
あと、フォントの縦幅って結構面倒くさくて、実際の文字サイズ+余白があります。
その余白はなにに使用するかというと、今回のXMLで小文字の「y」とか、下とか上にはみ出る文字がある時のためみたいです。
もし数字限定のTextViewで、余白も詰めたい!って人は、FontMetricsを詳しく調べると良いかもしれません(`・ω・´)

はてなブログでコード表示するときに、もうちょっと見やすくできないかなぁ...

Android端末の向きを固定する。

Android端末の向きを固定する方法をメモ。
調べてすぐ出てきたのは下記の2つ。

1.AndroidManifest.xmlandroid:screenOrientationを追加する。

<activity android:name=".Orientation" 
     android:label="@string/app_name" 
     android:screenOrientation="landscape">

上記だと、"landscape"が指定されているので横向き固定になる。
まとめると下記パターンがある。

  • "portrait"  縦
  • "landscape"  横
  • "unspecified" 端末の設定によって変化する?(向きの固定が事前されているとかかな…)
  • "sensor"   センサー状態に従う
  • "nosensor"  センダー状態に従わない以外はunspecifiedと同じ

     

2.setRequestedOrientationメソッドを呼んで設定する。

/** 画面の回転を抑制(現在の向きに固定) */
public void rotateLock(){
	Configuration config = getResources().getConfiguration();
	if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
	{
	    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
	}
	else if(config.orientation == Configuration.ORIENTATION_LANDSCAPE)
	{
	    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
	}
}

上記の関数を呼ぶと、現在の向きに固定する関数を作ってみました。
setRequestedOrientationに渡す引数によって固定され、渡すパターンは下記。

  • "SCREEN_ORIENTATION_PORTRAIT"  縦
  • "SCREEN_ORIENTATION_LANDSCAPE"  横
  • "SCREEN_ORIENTATION_UNSPECIFIED" 端末の設定によって変化する
  • "SCREEN_ORIENTATION_SENSOR"   センサー状態に従う
  • "SCREEN_ORIENTATION_UNSENSOR"  センダー状態に従わない以外はUNSPECIFIEDと同じ

現在の向きに固定したいのであれば、上記の関数を呼んでしまう。
固定を解除したいのであれば、"SCREEN_ORIENTATION_UNSPECIFIED"を指定して「setRequestedOrientation()」を呼んであげると固定解除になる。
正直、センサーの状態に従う、従わないがよくわからない…(´・ω・`)
     

Android端末の向きを取得する。

Android端末の向きを取得する。
画面の向きが、縦か、横かを取得する方法を関数形式でメモ。

public boolean GetRotateState()
{
  /* 現在の設定情報を取得 */
  Configuration config = getResources().getConfiguration(); //①
    if (config.orientation == Configuration.ORIENTATION_PORTRAIT) //②
    {
    	/* 現在の向きが縦の場合 */
    	return false;
    }
    else
    {
    	/* 現在の向きが横の場合 */
    	return true;
    }
}

①.ActivityのConfigurationを取得します。
②.取得した、Configurationのorientationフィールドと比較して、縦ならfalseを、横ならtrueを返す。

端末の向きが縦か、横かを取得する簡単な関数になっていますが、実際にはこれでは考慮不足かもしれません。
Configurationのorientationフィールドに入る可能性があるのは、下記の4パターン。

1.ORIENTATION_LANDSCAPE = 2 「横向き」
2.ORIENTATION_PORTRAIT = 1 「縦向き」
3.ORIENTATION_SQUARE = 3 「正方形?」APILevel16から廃止
4.ORIENTATION_UNDEFINED = 0 「未設定?」

正方形と、未設定の場合も一応ありえるらしいので、対応する形にするのが最善なのかもしれない・・・

dpとは?

記事のタイトルどおりのことだけど、アプリを作る上で、出来るだけ多くの端末に対応したいって考えると思います。

それを実現するためにあるのは知ってたけど、いまいちわかってなかった「dp」という単位について、簡単にまとめてみます!

 

まずは「px」について。

1.px = スクリーン上でのドット 

 px指定だと解像度が変更されたときに、表示が変わってしまうので、これは使用できない。Androidリファレンスなどでも推奨されていません。

 たとえば、1280x800の端末向けに作ったアプリを「px」指定で作成して、800x480の端末にインストールしても、レイアウトが収まりません。

 あたりまえですね(´・ω・`)

 

2.dp = 密度非依存ピクセル

 Android独自の?単位らしいですが、仮想的なピクセル単位を表します。画面の密度に依存せずにサイズを指定できます。

 ※密度とは?

 端末の画面には、解像度以外に密度というのがあります。キャリアの端末スペックなどを見れば書いてあることがありますが、それ以外では密度を調べるアプリなどがあるようなのでそれで確認できます。

 密度は、端末によって違いますが、密度が160密度なら「1dp=1px」ということになります。Galaxy nexusの密度は320なので「1dp=2px」ということになります。

 なのでdpを指定してあげるとアプリを起動したときに自動的にサイズを計算してくれるので、密度が変わっても表示サイズは変わりません。

 

 ですがここで注意したいのはあくまで密度だけが変わったらの場合です。

 dp指定で作ったレイアウトは、解像度が変わらず、密度が変わった場合には有効と言えます。

 

 なのでまとめとしては、

  px = 解像度が、同じ端末に対応したい場合。

  dp   =    解像度が同じかつ、画面密度が変わっても同じように表示したい場合。

 ということになるでしょう。

 

 なので全ての端末に対応するためには「dp」だけではなく、方法としては、

 weightを指定する、レイアウトを密度によってレイアウトのフォルダを分ける、など色々方法があると思います。解像度や密度によってフォルダを分けるのは確実でよいですが、どんどん新しい端末がでてくるし、解像度も密度も違うので、そのたびに対応する必要があります・・・。

 

 本当に今出ている端末すべてに対応する!って考えるとすごい手間がかかるし、工数もそれだけ増えるし、大変ですよね(´-ω-`)

 結構調べてみても簡単に対応するってことは、できなそうダナーって感じでした。

 

 

 

pointerIndex out of range が修正される?

以前、「pointerIndex out of range」についての記事を書きました↓

http://monakap.hatenablog.com/entry/2012/04/12/215721

 

前回の記事では、SDK-Japanに相談しました。

そこで相談に乗ってくれた、有山さんという方が不具合を見つけて、AOSPにパッチを提出してくれました。

 

ここまででも十分すごいことなのに、ついにそのパッチが承認されました!

それが以下のURL。

https://android-review.googlesource.com/#/c/33990/

 Status が Merged になっていますね。キタ ━━━ヽ(´・ω・`)ノ ━━━!!

 

だけどここから、自分もあまりよくわかってないんだけどAOSPで承認されてもすぐにアップデートがくるわけではないみたい?当たり前か(´・ω・`)

たとえば今回、マージされたものを「Android-GitHub」というところで確認してみました。

https://github.com/android

 

このURL先を見る感じAndroid4.0.4にマージされたっぽい?

なので使用する端末がAndroid4.0.4にアップデートされないとこのエラーが解決されたことにならないということなのかも。

 

「Android4.0.4 マルチタップ」とかで、グーグル先生に聞くと修正された~とか書かれているのをみるので、期待していいのかもしれない!

 

そういえば、GalaxyNexusの生産が終わっててちょっとしょんぼりしました・・

 

*2012/05/30追記*

EclipseAndroid関係のアップデートをしたら、エラーが起きなくなってました(´・∀・`)

本体のアップデートじゃなくて、開発側のほうをアップデートすればたぶん、適用されるっぽいですね!

 

pointerIndex out of range が発生? マルチタップエラー

ブログ始めたら、まずこの事について書きたかった!

 

「pointerIndex out of range」この例外について調べてここにたどり着いた人は、Android4.0以降で開発していて、マルチタップ関係の処理で発生したんじゃないかなと思う。

 

ていうかボクがそうでした(´・ω・`)

 

まずマルチタップを検出しようとしたら、ScaleGestureを使うか、MotionEventから「MotionEvent.getX(0)」とかで指ごとの座標を取得する必要があると思う。

 

だけどAndroid4.0の時点でマルチタップを検出する時、稀に「pointerIndex out of range」が発生して、アプリが強制終了する。

 

調査した結果、Android4.0ではマルチタップの検出に不具合があるらしい・・・

 

まずこのURL

http://freesoftking.blog52.fc2.com/blog-entry-309.html

この記事は、「GalaxyNexusに不具合」となっているけど、結局はAndroid4.0の不具合という記事。

SDKの不具合だと自分はまだ知識不足・・・

 

なので解決方法がわからないのでAndroid-SDK-Japanへ助けを求めました。

質問した場所はここ。

https://groups.google.com/forum/?hl=ja&fromgroups#!topic/android-sdk-japan/JVlYcLbUSXM

そして有山さんという偉大なお方が、原因を発見してくれました!

そしてさらに、AOSPへパッチも提出してくれるという手際のよさ・・・!

 

AOSPへ提出したパッチが承認されれば、「pointerIndex out of range」は発生しなくなると思います。

早く承認してくれええ!

マルチタップが使えないスマホとか、悲しすぎるよ(´-ω-`)