2012年2月24日金曜日

Android TextView の setTextSize() は sp 単位だよ!

いままでも sp という単位が用意されていましたが、標準の設定アプリでシステムの文字スケールを 設定できないかったので、ほぼ dp と同じような振る舞いでした。
Android 4.0 の設定アプリではシステム全体の文字スケールが設定できるようになっています。

[Display] - [Font size]



これによって sp 単位で指定した値はシステム全体の文字スケールに応じて実際のピクセル数が変わります。

Small


Normal


Large


Extra large


で、ここからが本題。

TextView の setTextSize() メソッドの引数は sp 単位として処理されます。

---
public void setTextSize (float size)

Set the default text size to the given value, interpreted as "scaled pixel" units. This size is adjusted based on the current density and user font size preference.

Related XML Attributes
android:textSize

Parameters
size The scaled pixel size.
---

コンテンツの本文とかはこれでいいのですが、ヘッダー部分などシステム全体の文字スケールに影響されたくない部分もあります。 そういう場合に dp など sp 以外の単位で指定するには、引数を2つとる setTextSize() を使います。

---
public void setTextSize (int unit, float size)

Set the default text size to a given unit and value. See TypedValue for the possible dimension units.

Related XML Attributes
android:textSize

Parameters
unit The desired dimension unit.
size The desired size in the given units.
---

第1引数で単位として TypedValue の定数を指定します。

  • COMPLEX_UNIT_DIP : dp, dip
  • COMPLEX_UNIT_MM : mm
  • COMPLEX_UNIT_PT : pt
  • COMPLEX_UNIT_PX : raw pixels
  • COMPLEX_UNIT_SP : sp
  1. public class MainActivity extends Activity {  
  2.       
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.main);  
  7.           
  8.         DisplayMetrics matrics = getResources().getDisplayMetrics();  
  9.           
  10.         float density = matrics.density;  
  11.         float scaledDensity = matrics.scaledDensity;  
  12.           
  13.         ((TextView)findViewById(R.id.density)).setText("density : " + density);  
  14.         ((TextView)findViewById(R.id.scaledDensity)).setText("scaledDensity : " + scaledDensity);  
  15.           
  16.         TextView tv1 = (TextView) findViewById(R.id.text1);  
  17.         tv1.setText("setTextSize(20)");  
  18.         tv1.setTextSize(20);  
  19.           
  20.         TextView tv2 = (TextView) findViewById(R.id.text2);  
  21.         tv2.setText("setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20)");  
  22.         tv2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);  
  23.     }  
  24. }  





■ sp, から pt, px に変換する

dp から pt に変換する方法(Android dip, dp, から pt, px に変換する)と同じように

getContext().getResources().getDisplayMetrics().scaledDensity

で文字スケールも考慮された比率がとれるので、 これを sp 単位の値にかければ pt, px 単位になります。


2012年2月23日木曜日

Android DrawableState でリストアイテムの背景を変える ViewGroup を作る

アイテムを選択可能なリスト用として、 CheckedTextView を使った

android.R.layout.simple_list_item_single_choice

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!-- Copyright (C) 2008 The Android Open Source Project  
  3.   
  4.      Licensed under the Apache License, Version 2.0 (the "License");  
  5.      you may not use this file except in compliance with the License.  
  6.      You may obtain a copy of the License at  
  7.     
  8.           http://www.apache.org/licenses/LICENSE-2.0  
  9.     
  10.      Unless required by applicable law or agreed to in writing, software  
  11.      distributed under the License is distributed on an "AS IS" BASIS,  
  12.      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13.      See the License for the specific language governing permissions and  
  14.      limitations under the License.  
  15. -->  
  16.   
  17. <CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"  
  18.     android:id="@android:id/text1"  
  19.     android:layout_width="match_parent"  
  20.     android:layout_height="?android:attr/listPreferredItemHeightSmall"  
  21.     android:textAppearance="?android:attr/textAppearanceListItemSmall"  
  22.     android:gravity="center_vertical"  
  23.     android:checkMark="?android:attr/listChoiceIndicatorSingle"  
  24.     android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"  
  25.     android:paddingRight="?android:attr/listPreferredItemPaddingRight"  
  26. />  




android.R.layout.simple_list_item_multiple_choice

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!-- Copyright (C) 2008 The Android Open Source Project  
  3.   
  4.      Licensed under the Apache License, Version 2.0 (the "License");  
  5.      you may not use this file except in compliance with the License.  
  6.      You may obtain a copy of the License at  
  7.     
  8.           http://www.apache.org/licenses/LICENSE-2.0  
  9.     
  10.      Unless required by applicable law or agreed to in writing, software  
  11.      distributed under the License is distributed on an "AS IS" BASIS,  
  12.      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13.      See the License for the specific language governing permissions and  
  14.      limitations under the License.  
  15. -->  
  16.   
  17. <CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"  
  18.     android:id="@android:id/text1"  
  19.     android:layout_width="match_parent"  
  20.     android:layout_height="?android:attr/listPreferredItemHeight"  
  21.     android:textAppearance="?android:attr/textAppearanceListItem"  
  22.     android:gravity="center_vertical"  
  23.     android:checkMark="?android:attr/listChoiceIndicatorMultiple"  
  24.     android:paddingLeft="8dip"  
  25.     android:paddingRight="8dip"  
  26. />  


が用意されていて

  1. public class MainActivity extends ListActivity {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.   
  7.         String[] data = { "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",  
  8.                 "Data", "Data", "Data", "Data", };  
  9.   
  10.         ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_single_choice, android.R.id.text1, data);  
  11.         setListAdapter(adapter);  
  12.           
  13.         ListView lv = getListView();  
  14.         lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);  
  15.     }  
  16. }  


のように使うことで



のようにできます。


さて、

1行のレイアウトを複雑にしたい場合、内部に CheckBox を持ってその表示を変える方法に ついては Android Layout Cookbook に書きました。

CheckBox ではなく、単に1行の ViewGroup の背景色を変えて選択状態を変えたい場合は Checkable を implements した ViewGroup を作ります。

例えばリストの1行を LinearLayout にして、選択されたら背景を変えるようにするには、 このように LinearLayout を継承したクラスを作って Checkable を実装します。

CheckedLinearLayout.java
  1. public class CheckedLinearLayout extends LinearLayout implements Checkable {  
  2.     private boolean mChecked;  
  3.   
  4.     private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };  
  5.   
  6.     public CheckedLinearLayout(Context context) {  
  7.         this(context, null);  
  8.     }  
  9.   
  10.     public CheckedLinearLayout(Context context, AttributeSet attrs) {  
  11.         this(context, attrs, 0);  
  12.     }  
  13.   
  14.     public CheckedLinearLayout(Context context, AttributeSet attrs, int defStyle) {  
  15.         super(context, attrs, defStyle);  
  16.     }  
  17.   
  18.     @Override  
  19.     public void toggle() {  
  20.         setChecked(!mChecked);  
  21.     }  
  22.   
  23.     @Override  
  24.     public boolean isChecked() {  
  25.         return mChecked;  
  26.     }  
  27.   
  28.     @Override  
  29.     public void setChecked(boolean checked) {  
  30.         if (mChecked != checked) {  
  31.             mChecked = checked;  
  32.             refreshDrawableState();  
  33.         }  
  34.     }  
  35.   
  36.     @Override  
  37.     protected int[] onCreateDrawableState(int extraSpace) {  
  38.         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);  
  39.         if (isChecked()) {  
  40.             mergeDrawableStates(drawableState, CHECKED_STATE_SET);  
  41.         }  
  42.         return drawableState;  
  43.     }  
  44. }  


Checkable インタフェースのメソッドは toggle(), isChecked(), setChecked() だけなのですが、 これを実装するだけでは背景は変わってくれません。
キモは onCreateDrawableState() で、ここで super.onCreateDrawableState() で LinearLayout の drawableState を取得し、それにチェック状態の state を mergeDrawableStates() で追加したものを返す必要があります。

MainActivity.java
  1. public class MainActivity extends ListActivity {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.   
  7.         String[] data = { "Data""Data""Data""Data""Data""Data""Data""Data""Data""Data""Data",  
  8.                 "Data""Data""Data""Data", };  
  9.   
  10.         ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, android.R.id.text1, data);  
  11.         setListAdapter(adapter);  
  12.           
  13.         ListView lv = getListView();  
  14.         lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);  
  15.     }  
  16. }  


list_item.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <yanzm.example.drawablestatesample.CheckedLinearLayout   
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="wrap_content"  
  6.     android:background="@drawable/list_item_bg"  
  7.     android:gravity="center_vertical"  
  8.     android:orientation="vertical"  
  9.     android:padding="8dip" >  
  10.   
  11.     <TextView  
  12.         android:id="@android:id/text1"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:textAppearance="?android:attr/textAppearanceListItem" />  
  16.   
  17.     <TextView  
  18.         android:layout_width="wrap_content"  
  19.         android:layout_height="wrap_content"  
  20.         android:text="sub text"  
  21.         android:textAppearance="?android:attr/textAppearanceListItemSmall" />  
  22.   
  23. </yanzm.example.drawablestatesample.CheckedLinearLayout>  


list_item.bg
  1. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <item android:state_checked="true">  
  4.         <shape>  
  5.             <solid android:color="#666666" />  
  6.         </shape></item>  
  7.     <item>  
  8.         <shape>  
  9.             <solid android:color="#00000000" />  
  10.         </shape>  
  11.     </item>  
  12.   
  13. </selector>  


こんな感じにタップした行の背景が変わるようになります。





2012年2月15日水曜日

Android コードから PopupWindow を生成するときは setBackgroundDrawable() したほうがいいよ

こんにちは。お久しぶりです。
原稿とか原稿とかメルマガとかで、ご無沙汰してたのですが、ぼちぼち再開します。
原稿の行方についてはそのうちご報告できると思います。(早くはじめにを書けよ、というry)

---


PopupWindow には、setOutsideTouchable(true) を指定すると、PopupWindow の外の領域をタップしたときにポップアップを閉じる処理が実装されています。

PopupWindow.java#1578
  1. 1578         @Override  
  2. 1579         public boolean onTouchEvent(MotionEvent event) {  
  3. 1580             final int x = (int) event.getX();  
  4. 1581             final int y = (int) event.getY();  
  5. 1582   
  6. 1583             if ((event.getAction() == MotionEvent.ACTION_DOWN)  
  7. 1584                     && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {  
  8. 1585                 dismiss();  
  9. 1586                 return true;  
  10. 1587             } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {  
  11. 1588                 dismiss();  
  12. 1589                 return true;  
  13. 1590             } else {  
  14. 1591                 return super.onTouchEvent(event);  
  15. 1592             }  
  16. 1593         }  


また、setTouchInterceptor() で View.OnTouchListener を指定すると、ポップアップのタッチを取得することができます。
ちなみに、setOutsideTouchable(true) を指定しないと、ポップアップの外側をタッチしたときにリスナーが呼ばれません。

PopupWindow.java#1570
  1. 1570         @Override  
  2. 1571         public boolean dispatchTouchEvent(MotionEvent ev) {  
  3. 1572             if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {  
  4. 1573                 return true;  
  5. 1574             }  
  6. 1575             return super.dispatchTouchEvent(ev);  
  7. 1576         }  


問題は、これらポップアップのタッチイベントが PopupWindow の mBackground 変数が null の時は呼ばれない!!!ということなのです。

レイアウトXMLファイルから PopupWindow を生成した場合は

  1. 177     public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  2. 178         mContext = context;  
  3. 179         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);  
  4. 180   
  5. 181         TypedArray a =  
  6. 182             context.obtainStyledAttributes(  
  7. 183                 attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);  
  8. 184   
  9. 185         mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);  


のようにデフォルトの背景が指定されるので問題ないのですが、コードから生成する場合は PopupWindow の setBackgroundDrawable() メソッドを読んで mBackground に Drawable をセットしないと

  1. 312     public Drawable getBackground() {  
  2. 313         return mBackground;  
  3. 314     }  


setOutsideTouchable(true) を指定してもポップアップの周りをタップしても自動で dismiss() してくれないし、さらには setTouchInterceptor() で指定したリスナーの onTouch() も呼ばれなくなります!

実は、これらのタッチイベントの処理 (onTouchEvent や dispatchTouchEvent) は PopupWindow のサブクラスの PopupViewContainer の中に実装されています。この PopupViewContainer が mBackground が null 以外のときだけ使うような実装になっているのです。

PopupWindow.java#944
  1. 944     private void preparePopup(WindowManager.LayoutParams p) {  
  2.             ...  
  3. 950         if (mBackground != null) {  
  4.                 ...  
  5. 960             PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);  
  6. 961             PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(  
  7. 962                     ViewGroup.LayoutParams.MATCH_PARENT, height  
  8. 963             );  
  9. 964             popupViewContainer.setBackgroundDrawable(mBackground);  
  10. 965             popupViewContainer.addView(mContentView, listParams);  
  11. 966   
  12. 967             mPopupView = popupViewContainer;  
  13. 968         } else {  
  14. 969             mPopupView = mContentView;  
  15. 970         }  
  16.             ...  
  17. 973     }  


別に背景なくても PopupViewContainer 使ってくれればいいのにー。

ということで、とりあえず PopupWindow は背景指定したほうがいいよー。