Hatena::Groupxn--272ax3f

Android アプリ開発勉強会 #2

Android アプリ開発勉強会 #2

デバイス対応

  • Android には様々なバリエーション
    • 言語
    • 大きさ
    • OS のバージョン
  • これらの違いに対応しよう

多言語対応

  • UI文字列はコードとは別に管理しよう
    • Android に限った話じゃない
  • デフォルトの言語は res/values/strings.xml
    • the default language, which is the language that you expect most of your application's users to speak.*
  • デフォルト以外の言語は res/values-Language and region/strings.xml
    • ここでは ISO country code とあるが, 実際は The language is defined by a two-letter ISO 639-1 language code, optionally followed by a two letter ISO 3166-1-alpha-2 region code (preceded by lowercase "r").*
    • de とか ja とか ja-rJP とか
  • 文字列値はそれぞれのロケールの適切な xml ファイルに書く
  • ソースコードからは string 要素の name 属性で参照する
    • R.string.string_name
// Get a string resource from your app's Resources
String hello = getResources().getString(R.string.hello_world);

// Or supply a string resource to a method that requires a string
TextView textView = new TextView(this);
textView.setText(R.string.hello_world);
  • 別の XML ファイルからは @string/string_name で参照する
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />

スクリーンサイズ対応

  • スクリーンはサイズと密度をもつ
    • サイズ: small, normal, large, xlarge
    • 密度: ldpi, mdpi, hdpi, xhdpi, xxhdpi
      • それぞれだいたい 120dpi, 160dpi, 240dpi, 320dpi, 480dpi
  • 向きもあるよ
  • 画像とかは常にすべての密度に対応したものを提供すべき
    • res/drawable-density/ に配置

Android のバージョン対応

  • AndroidManifest.xml で最小/ターゲット API バージョンを指定
    • uses-sdk 要素の minSdkVersion 属性
      • 互換性のある最小の API バージョン
    • uses-sdk 要素の targetSdkVersion 属性
      • 設計/テストした最大のバージョン
      • 最新のものにすべき
private void setUpActionBar() {
    // Make sure we're running on Honeycomb or higher to use ActionBar APIs
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        ActionBar actionBar = getActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);
    }
}
  • プラットフォームで提供されているスタイルとかテーマを利用するとバージョンにあわせた見た目になる

まとめ: デバイス対応

  • UI文字列はコードとは別に管理しよう
  • スクリーンのバリエーションはサイズと密度と向き
  • 古いバージョンの Android も対応しよう

フラグメント

  • android.app.Fragment クラスは
    • 入れ子になった Activity のようなもの
      • それぞれレイアウトを持つ
      • それぞれのライフサイクルで管理される
    • 動的でマルチペインな UI を提供する
    • 異なるスクリーンサイズで違うフラグメントの組合せにできる

サポートライブラリの利用

  • いい感じに libs/ に配置しよう
    • IDE ならいい感じにしてくれるんじゃないでしょうか
    • こういうのは人の手でやるものではないのでいい感じにしましょう

フラグメントを作ろう

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.ViewGroup;

public class ArticleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.article_view, container, false);
    }
}
    • 他のコールバックもちゃんと実装しよう
      • アクティビティに追加されたり除去されたりでもコールバック呼ばれる
      • アクティビティ自身へのコールバックも
<!-- res/layout-large/news_articles.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <fragment android:name="com.example.android.fragments.HeadlinesFragment"
              android:id="@+id/headlines_fragment"
              android:layout_weight="1"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

    <fragment android:name="com.example.android.fragments.ArticleFragment"
              android:id="@+id/article_fragment"
              android:layout_weight="2"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

</LinearLayout>
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);
    }
}
  • xml の中に書かれているフラグメントはランタイムに削除できない
    • フラグメントの差し替えとか考えている場合はアクティビティの開始時に追加しないといけない

柔軟な UI を作ろう

  • 様々なスクリーンサイズに対応するのに Fragment を再利用したり, スクリーンサイズに最適なレイアウトにしたりしよう
  • Fragment の差し替えとか考えている場合は xml で定義するのではなくランタイムに追加しないといけない
    • FragmentManager から作られる FragmentTransaction が Fragment を追加, 削除, 置換する機能を提供する
    • Activity で Fragment を削除, 置換するには Activity#onCreate で最初の Fragment を追加しておくべき
    • ランタイムに追加する場合はレイアウトに Fragment のためのコンテナの View が必要
<!-- res/layout/news_articles.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);

        // Check that the activity is using the layout version with
        // the fragment_container FrameLayout
        if (findViewById(R.id.fragment_container) != null) {

            // However, if we're being restored from a previous state,
            // then we don't need to do anything and should return or else
            // we could end up with overlapping fragments.
            if (savedInstanceState != null) {
                return;
            }

            // Create an instance of ExampleFragment
            HeadlinesFragment firstFragment = new HeadlinesFragment();
            
            // In case this activity was started with special instructions from an Intent,
            // pass the Intent's extras to the fragment as arguments
            firstFragment.setArguments(getIntent().getExtras());
            
            // Add the fragment to the 'fragment_container' FrameLayout
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, firstFragment).commit();
        }
    }
}
  • フラグメントの置換は先の例の FragmentTransaction#add のかわりに FragmentTransaction#replace を使う
  • フラグメントのトランザクションはユーザーが undo できるようにすべき
    • FragmentTransaction#commit の前に FragmentTransaction#addToBackStack を呼ぶ
// Create fragment and give it an argument specifying the article it should show
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

フラグメント間のやりとり

  • フラグメント同士のやりとりは関連付けられているアクティビティを通す
    • フラグメント同士で直接やりとりすることはできない
public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // Container Activity must implement this interface
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Send the event to the host activity
        mCallback.onArticleSelected(position);
    }

    ...
}
public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener {
    ...

    public void onArticleSelected(int position) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article

        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // If article frag is available, we're in two-pane layout...

            // Call a method in the ArticleFragment to update its content
            articleFrag.updateArticleView(position);
        } else {
            // Otherwise, we're in the one-pane layout and must swap frags...

            // Create fragment and give it an argument for the selected article
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();
        }
    }
}

まとめ: フラグメント

  • フラグメントは個々に独立した UI コンポーネントでその組み合わせでアクティビティを構成する
  • 動的な UI を構成するには xml で定義できない
  • フラグメント同士のやりとりは親のアクティビティを介す