ラベル スニペット の投稿を表示しています。 すべての投稿を表示
ラベル スニペット の投稿を表示しています。 すべての投稿を表示

2012年2月23日木曜日

[Android] アクションバーを画面下側から表示させてみました。アプリ編

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

@adakodaさんが面白い事をしていらっしゃったのでインスパイア。
[Android] アクションバーを画面下側から表示させてみました
http://www.adakoda.com/adakoda/2012/02/android-47.html

アプリからでも出来るのでは?


アプリケーションではアプリを初期化する際にsetContentView()するんで、setContentView()で出来たView群が画面上の全てだと思いがちです。しかーし画面上に存在する以上タイトルバーやActionBarなども当然Viewの一種なわけです。ちゅーことはアプリケーションのトップレベルのViewを取れたらタイトルバーやActionBarも操作出来るのでは無いでしょうか!?

そもそもまずトップレベルのViewをアプリケーション側から取得する事が出来るのか。出来ます。ActicvityからWindowを取り出してgetDecorView()するだけ。
View root = activity.getWindow().getDecorView()

ではこのViewどういう構造になっているか。Viewツリーをダンプしてみましょう。ViewツリーのダンプはViewの階層構造をダンプするスニペットを参考に。
View v = getWindow().getDecorView();
Util.dumpViewTree(v, "");

吐かれたのがこれ。Android4.0.2のGalaxy Nexusで、プロジェクトはEclipse+ADTで生成して出来たものを全く触らずに利用。ターゲットは4.0です。

Util  D  com.android.internal.policy.impl.PhoneWindow$DecorView
Util  D   android.widget.LinearLayout
Util  D    com.android.internal.widget.ActionBarContainer
Util  D     com.android.internal.widget.ActionBarView
Util  D      android.widget.LinearLayout
Util  D       android.widget.ImageView
Util  D       android.widget.LinearLayout
Util  D        android.widget.TextView
Util  D        android.widget.TextView
Util  D      com.android.internal.widget.ActionBarView$HomeView
Util  D       android.widget.ImageView
Util  D       android.widget.ImageView
Util  D     com.android.internal.widget.ActionBarContextView
Util  D    android.widget.FrameLayout
Util  D     android.widget.LinearLayout
Util  D      android.widget.TextView
Util  D    com.android.internal.widget.ActionBarContainer

"com.android.internal.widget.ActionBarContainer"てのが居ますね。露骨ですね。なんで二個あるのかはちょっとわかりません。

findViewsWithClass()を拡張


じゃあ"com.android.internal.widget.ActionBarContainer"を取り出して操作したらいいんじゃまいか?どうやって取り出すの。ああ、何かそういうエントリを昨日見たよ。あった。List<T> findViewsWithClass(View v, Class<T>)。助かるわー。#ステマ

しかしどうもList<T> findViewsWithClass(View v, Class<T>)ではClass<T>を使っています。com.android.internal.widget.ActionBarContainerは名前しか判らないのでちょっと拡張する必要がありそうですね。

こうなった
public static <T extends View> List<T> findViewsWithClass(View v, Class<T> clazz) {
  List<T> views = new ArrayList<T>();
  findViewsWithClass(v, clazz.getName(), views);
  return views;
 }
 public static List<View> findViewsWithClassName(View v, String className) {
  List<View> views = new ArrayList<View>();
  findViewsWithClass(v, className, views);
  return views;
 }
 private static <T extends View> void findViewsWithClass(View v, String clazz, List<T> views) {
  if (v.getClass().getName().equals(clazz)) {
   views.add((T) v);
  }
  if (v instanceof ViewGroup) {
   ViewGroup g = (ViewGroup) v;
   for (int i = 0; i < g.getChildCount(); i++) {
    findViewsWithClass(g.getChildAt(i), clazz, views);
   }
  }
 }

これでcom.android.internal.widget.ActionBarContainerを簡単に取り出せます。

View root = activity.getWindow().getDecorView();
List<View> views = Util.findViewsWithClassName(root, "com.android.internal.widget.ActionBarContainer");



試しにsetVisibility(View.GONE)してみる


では試しに取り出したcom.android.internal.widget.ActionBarContainerをView.GONEで消してみましょう。

View root = activity.getWindow().getDecorView();
List<View>l views = Util.findViewsWithClassName(root, "com.android.internal.widget.ActionBarContainer");
for(View v : views){
  v.setVisibility(View.GONE);
}

消えたー!操作出来る。当たり前だけど。


actionBarUpsideDown(Activity activity)


Viewツリーのダンプを見ると、事実上ルートのコンテナはcom.android.internal.policy.impl.PhoneWindow$DecorView直下のLinearLayout。多分Vertical。つまり、この人にいるcom.android.internal.widget.ActionBarContainerをremoveView()で消して、addView()で足すとケツに移動するのではないか!?
という事でメソッドを作成。

public final static void actionBarUpsideDown(Activity activity) {
  View root = activity.getWindow().getDecorView();
  View firstChild = ((ViewGroup) root).getChildAt(0);
  if (firstChild instanceof ViewGroup) {
   ViewGroup viewGroup = (ViewGroup) firstChild;
   List<View> views = Util.findViewsWithClassName(root, "com.android.internal.widget.ActionBarContainer");
   if (!views.isEmpty()) {
    for (View vv : views) {
     viewGroup.removeView(vv);
    }
    for (View vv : views) {
     viewGroup.addView(vv);
    }
   }
  }
  else{
   Log.e(TAG, "first child is not ViewGroup.");
  }
 }

試す


メソッドにしたので試すのは簡単ですね

setContentView(R.layout.main);
Util.actionBarUpsideDown(this); 

キタワァ.*・゜゚・*:.。..。.:*・゜(n‘∀‘)η゚・*:.。. .。.:*・゜゚・*!!!!!☆



まとめ


ICS以外で呼ぶとどうなるか試してないので適宜何とかして下さい。
ActionBarが上に居るのは個人的に良く思っていないので、これ使って下に表示させる様にしていきたい。



Viewの階層構造をダンプするスニペット

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
まぁViewっていうかViewGroupですけど。

コード


こうです。あんまり階層が深いとスタックオーバーフローで死ぬと思います。
public static void dumpViewTree(View v, String padding){
  Log.d(TAG, padding + v.getClass().getName());
  if(v instanceof ViewGroup){
   ViewGroup g = (ViewGroup)v;
   for(int i = 0; i < g.getChildCount(); i++){
    dumpViewTree(g.getChildAt(i), padding+" ");
   }
  }
 }

実行

試しにandroid.support.v4.app.ListFragmentのonCreateView()で自動的に生成されるViewをダンプしてみました。 こういう構造してたんですねー。
27152 Util  D  android.widget.FrameLayout
 27152 Util  D   android.widget.LinearLayout
 27152 Util  D    android.widget.ProgressBar
 27152 Util  D   android.widget.FrameLayout
 27152 Util  D    android.widget.TextView
 27152 Util  D    android.widget.ListView

まとめ


デバッグとか動的にView書き換えてる場合とかAPIが勝手に生成するViewについて調べるのに使えるのではないでしょうかー。





2012年2月22日水曜日

List<T> findViewsWithClass(View v, Class<T>)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

android.view.ViewといえばfindViewById(int)ですよねー。
実はそれ以外にもfindViewWithTag (Object tag)やfindViewsWithText(ArrayList, CharSequence, int)てのがあるようです。
使い所がよくわかりませんがなんか使えそうですね。

個人的にはViewのクラス名で取り出したいなーと、例えばjavascriptのgetElementsByTagName(name)のノリだと言うと判ると思います。

List<T> findViewsWithClass(View v, Class<T>)


はい、完成。本当は v instanceof Tってやりたかったけど怒られました。仕方なくClassのフルネームで比較してます。なのでサブクラスとかまでは検出できません。

public static <T extends View> List<T> findViewsWithClass(View v, Class<T> clazz){
  List<T> views = new ArrayList<T>();
  findViewsWithClass(v, clazz, views);
  return views;
 }
 
 private static <T extends View> void findViewsWithClass(View v, Class<T> clazz, List<T> views){
  if(v.getClass().getName().equals(clazz.getName())){
   views.add((T)v);
  }
  if(v instanceof ViewGroup){
   ViewGroup g = (ViewGroup)v;
   for(int i = 0; i < g.getChildCount(); i++){
    findViewsWithClass(g.getChildAt(i), clazz, views);
   }
  }
 }

※追記20120223
@zaki50さんからツッコミが。ありがとうございます!!



直した!。これの場合だとサブクラスまでがっつり取れる。

 private static <T extends View> void findViewsWithClass(View v, Class<T> clazz, List<T> views) {
  if (clazz.isAssignableFrom(v.getClass())){
   views.add(clazz.cast(v));
  }
  if (v instanceof ViewGroup) {
   ViewGroup g = (ViewGroup) v;
   for (int i = 0; i < g.getChildCount(); i++) {
    findViewsWithClass(g.getChildAt(i), clazz, views);
   }
  }
 }

使う


こう使うとよさげ。例えばこういうレイアウト

<?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" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="mogeee" >
    </TextView>
    <ImageView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" >
    </ImageView>
    <LinearLayout
        android:id="@+id/main_list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    </LinearLayout>
</LinearLayout>

こういうコードで要素を取り出せます。以下はTextViewを取り出してます。

List<TextView> views = findViewsWithClass(v, TextView.class);
        if(views.isEmpty()){
         Log.d(TAG, "not found");
        }
        else{
         Log.d(TAG, "find:"+views.size());
        }

2011年9月19日月曜日

【java】closeする用メソッド

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
多分便利なライブラリがあるんだろうなーと思いつつ、ファイル操作はいつもFileInputStream,InputStreamReader,BufferedReaderなどを使ったりしていて、finallyのcloseとかめんどくさい感じになっていて、以下のメソッドをいつも使っているけど意外と便利だったのでメモ。

コード


どんなオブジェクトをぶち込んでもとにかくcloseしてくれる君。
void close()なメソッド持って無くてもスタックトレースが吐かれまくるだけ。

public static void close(Object hasCloseMethod) {
	if (hasCloseMethod == null) {
		return;
	}
	try {
		hasCloseMethod.getClass().getMethod("close", (Class< ? >[]) null).invoke(hasCloseMethod, (Object[]) null);
	} catch (Exception e) {
		e.printStackTrace();
	}
}


まとめ


なんかいいライブラリ教えて下さい・・・。

2011年4月27日水曜日

【Android】 cancelableがfalseなDialogをタイムアウトでcancelするスニペット

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ほぼ、そういう機会は無いだろうなーと思いますが、
ProgressDialogなどをshowした後、一定時間経ったらタイムアウトさせてcancelさせたい場合があるかもしれません。
さらにダイアログはcancelableがfalseで、ユーザからキャンセルは出来ない様にしたいとか。
そういうまぁなかなか無い事をしたい時に使えるスニペットです。

モノ


以下のクラスをどっかに作ります。


public class DialogTimeout{
 public void setTimeout(final Dialog dialog, long timeout){
  new Handler().postDelayed(new Runnable() {
   @Override
   public void run() {
    if(dialog.isShowing()){
     dialog.cancel();
    }
   }
  }, timeout);
 }
}

あとはsetTimeoutでDialogとタイムアウトの時間を指定すると、指定時間経過後にcancelが実行され、
onCancelListenerのonCancelが呼ばれます。
cancelを呼ぶ際は、isShowing()でダイアログがまだ有効か確認しています。

使い方


まぁ普通にshowとかした後タイムアウトの時間指定して突っ込むだけ。

ProgressDialog dialog = new ProgressDialog(this);
        dialog.setIndeterminate(true);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setCancelable(false);
     dialog.setTitle("ダイアログ");
        dialog.setMessage("処理中だよー");
        dialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
             Toast.makeText(getBaseContext(), "タイムアウトしたっぽい", Toast.LENGTH_SHORT).show();
                finish();
            }
        });
        dialog.show();
        DialogTimeout.setTimeout(dialog, 5000);

まとめ


タイトルがルー大柴な気がする。

2011年2月4日金曜日

【Android】【スニペット】画面密度から画像等々をどれだけスケールさせたらいいかをコード上で取得する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
画面密度が異なると、dipで表現した長さが異なります。
この仕組によって大きなスクリーン(高密度)、小さなスクリーン(低密度)でも概ね比率が損なわれる事なくコンポーネント等をレイアウトできます。
ところがこの便利なdipの表現がコード上で動的にできません。(多分)
layout.xmlでは出来るんでまーいいんですが、たまーに動的にやりたい気分の時もあります。
どーしたらいいかというと、DisplayMetricsという画面の情報を持っているクラスのscaledDensityを使います。
scaledDensityには画面密度に対するpxの倍率が入ってます。
だいたい高密度のhdpiの端末(Desire,Nexsus系,IS03とか、GalaxySとか)は1.5が入ってます。
100pxのボタンがあったとしたら、150pxすると比率的にいいという事になります。
480x320(mdpi)の場合scaledDensity=1で100pxは幅の31.25%
800x480(hdpi)の場合scaledDensity=1.5で150pxは幅の31.25%!
とまぁscaledDensityで大体同じ比率のレイアウトが出来るんですね~

で,そのscaledDensity。取得が結構面倒です。
scaledDensityが取りたいからってあちこちのコードで取り出してたら面倒なんで、スニペット作りました。
以下のstaticメソッドをどっかに書いておけばいつでもscaledDensityが取り出せます。
public static float getDisplayScale(Context context){
WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm.scaledDensity;
}

使った結果はこんなん。若干分かりにくいですが画面幅に対して大体同じ大きさになってますね。
100px*100pxの画像をBitmapで読み込んでスケールして出してます。

480x320(mdpi)













800x480(hdpi)













※ただ800x480の癖にmdpiでscaledDensityが1の不届きな端末もあります。その場合表示が腐りますのでご注意下さい。
コードはこんな感じ
ImageView imgView = (ImageView)findViewById(R.id.img);
Bitmap img = BitmapFactory.decodeResource(getResources(), R.drawable.data);
Matrix m = new Matrix();
float scale = getDisplayScale(this);
m.setScale(scale, scale);
Bitmap scaleImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), m, false);
imgView.setImageBitmap(scaleImg);