個人Android 4 學習系列之建立用戶基本界面

目錄
  • 使用視圖和佈局
  • 理解Fragment
  • 優化佈局
  • 建立分辨率無關的用戶界面
  • 擴展、分組、建立和使用視圖
  • 使用適配器將數據綁定到視圖
使用視圖和佈局

1. Android UI 幾個基本概念java

  • 視圖: 全部可視界面的元素(一般稱爲控件或者小組件)的基類。全部的UI控件(包括佈局類)都是由 View 派生而來的。
  • 視圖組:視圖類的擴展,能夠包含多個子視圖。經過擴展ViewGroup類,能夠建立由多個相互鏈接的子視圖組成複合控件;還能夠經過擴展ViewGroup類來提供佈局管理器,以幫助在Acitivity里布局控件。
  • Fragment:Fragment在Android 3.0(API level 11)中引入,用於UI的各個部分。

這種封裝使得Fragment特別適合針對不一樣的屏幕尺寸優化UI佈局以及建立可重用的UI元素。android

每一個Fragment都包含本身的UI佈局,並接受相關的輸入事件,可是必須與包含它們的Acitivity緊密綁定在一塊兒,也就是說Fragment必須簽入到Activity。canvas

  • Activity: 顯示給用戶的窗口或者屏幕。要顯示UI,就須要給Activity分配一個View(一般是一個佈局或者Fragment)。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        resource
//        setContentView(R.layout.activity_main);
//        code
        TextView myTextView = new TextView(this);
        setContentView(myTextView);
        myTextView.setText("Hello, Android!");
    }數組

2. 佈局app

佈局管理器(也叫佈局)是對ViewGroup的擴展,它用來在控制子控件在UI中位置的。能夠嵌套。構建複雜界面。ide

最經常使用的佈局類:模塊化

  • FrameLayout  最簡單的佈局管理器。默認從左上角開始堆放,能夠用gravity屬性改變其位置。添加多個子視圖,新的子視圖堆積在前一個子視圖上面,並且每個新的子視圖可能會遮擋住上一個。
  • LinearLayout  線性佈局管理器。在垂直方向或者水平方向上對齊每個子視圖。在垂直方向佈局會有一個佈局列,水平方向則有一個佈局行。LinearLayout容許爲每個子視圖制定一個屬性Weight,以控制子視圖在可用空間內的大小。
  • RelativeLayout  能夠定義每個子視圖與其餘子視圖之間以及屏幕邊界之間的相對位置。
  • GridLayout  Android 4.0(API Level 14)中引入,由極細的線構成的矩形網格,在一系列的行和列中佈局。能夠簡化佈局,消除嵌套,不要本身手動調整XML

--------------------------------Resource--------------------------------------函數

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Enter Text Bellow"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Text Goes Here!"/>
</LinearLayout>工具

android:layout_width="match_parent":用來擴展視圖,使其填滿父視圖、Fragment和Activity內的可用空間。佈局

android:layout_height="match_parent":設定包含它顯示內容所需的最小尺寸(好比:顯示換行文字字符串所需高度)。

-------------------------------Code-------------------------------------------

LinearLayout ll = new LinearLayout(this);
        ll.setOrientation(LinearLayout.VERTICAL);
        TextView myTextView = new TextView(this);
        EditText myEditText = new EditText(this);
        myTextView.setText("Enter Text Below");
        myEditText.setText("Text Goes Here!");
        int lHeight = LinearLayout.LayoutParams.MATCH_PARENT;
        int lWidth = LinearLayout.LayoutParams.WRAP_CONTENT;
        ll.addView(myTextView, new LinearLayout.LayoutParams(lWidth, lHeight));
        ll.addView(myEditText, new LinearLayout.LayoutParams(lWidth, lHeight));
        setContentView(ll);

3. 使用佈局

佈局的關鍵特徵是可以適應各類各樣的屏幕尺寸、分辨率和屏幕方向。

(1)LinearLayout:

大多數時候,你會用線性佈局來構建一些UI元素,而後把這些UI元素嵌套到其餘佈局中,好比相對佈局。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <Button
            android:text="@string/cancel_button_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <Button
            android:text="@string/ok_button_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>
    <ListView
        android:layout_width="match_parent"
        android:layout_height="fill_parent"></ListView>
</LinearLayout>

運行效果:

image

(2)RelativeLayout:

很是靈活的佈局方式,容許根據父元素或其餘視圖的位置定義每一個元素在佈局中的位置。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:id="@+id/button_bar"
        android:layout_alignParentBottom="true"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <Button
            android:text="@string/cancel_button_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <Button
            android:text="@string/ok_button_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>
    <ListView
        android:id="@+id/listview_bar"
        android:layout_alignParentLeft="true"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"></ListView>
</RelativeLayout>

image

(3)GridLayout:

全部佈局管理器中最爲靈活的一種。

GridLayout使用一個隨意選擇的網格來放置視圖。經過使用行和列延伸、Space View和Gravity屬性,能夠建立複雜的UI。

對於構建須要在兩個方向上進行對齊的佈局,網格佈局特別有用。

出於性能考慮,優先考慮網格佈局,而不是嵌套佈局。

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:background="#FF444444"
        android:layout_gravity="fill"></ListView>
    <LinearLayout
        android:id="@+id/button_bar"
        android:layout_gravity="fill_horizontal"
        android:orientation="horizontal"
        android:padding="5dp">
        <Button
            android:text="@string/cancel_button_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <Button
            android:text="@string/ok_button_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>
    <ListView
        android:id="@+id/listview_bar"
        android:layout_alignParentLeft="true"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"></ListView>
</GridLayout>

使用網格佈局時,不須要指定寬度和高度。這是由於每一個元素默認都會包圍其元素,並且layout_gravity屬性被用來肯定每一個元素應該在那個方向上延伸。

image

4. 優化佈局

(1)避免佈局冗餘:

merge的使用:當包含有merge標籤的佈局添加到另外一佈局時,該佈局的merge節點會被刪除,而該佈局的子View會被直接添加到新的父佈局中。

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <ImageView
        android:id="@+id/myImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/myimage"/>
    <TextView
        android:id="@+id/myTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        android:layout_gravity="center_horizontal"
        android:gravity="center_horizontal"/>
</merge>

merge標籤結合 include標籤一塊兒使用尤爲有用,include標籤是用來把一個佈局的內容插入到另外一個佈局中。

image_text_layout.xml ---------

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <ImageView
        android:id="@+id/myImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/myimage"/>
    <TextView
        android:id="@+id/myTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        android:layout_gravity="center_horizontal"
        android:gravity="center_horizontal"/>
</merge>

activity_main.xml ---------

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <include
        android:id="@+id/my_image_text_layout"
        layout="@layout/image_text_layout"/>
</LinearLayout>

image

(2)避免使用過多的View

佈局的View數不能超過80,想要在複雜的UI填充View數量變少,可使用ViewStub。

ViewStub 就像延遲填充的include標籤 ----- 一個stub表明了在父佈局中指定多個子View ---- 但只有在顯示的調用inflate()方法或被置爲可見的時候,這個stub纔會被填充。

<ViewStub
        android:id="@+id/my_hello_stub"
        android:layout="@layout/image_text_layout"
        android:inflatedId="@+id/image_text_layout_hello"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"/>

當分別使用id 和 inflatedId 屬性填充View時,一個ID已經被分配給StubView和它將成爲的ViewGroup了。

當ViewStub被填充,它就會從視圖層中刪除掉且被它導入的View根節點所替換。若是要修改View的可見性,必須使用他們根節點的引用(經過inflate調用返回)或者經過findViewById方法找到那個View,該方法使用在相應的ViewStub節點中分配給該View的佈局ID。

(3)使用Lint工具來分析佈局:LInt工具可以分析佈局的性能問題。

Lint能夠檢測UI佈局性能,缺乏的翻譯,未使用的資源、不一致的數組大小、可訪問性和國際化問題、丟失或重複的圖像資源,可用性問題和manifest錯誤。

5. 實踐:To-Do List

一個待辦事項列表;一個添加新待辦事項的文本框。

Resource :

--------- activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <EditText
        android:id="@+id/myEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/addItemHint"
        android:contentDescription="@string/addItemDescription"/>
    <ListView
        android:id="@+id/myListView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

------- strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">To Do List</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="cancel_button_text">Cancel</string>
    <string name="ok_button_text">OK</string>
    <string name="addItemHint">New To Do Item</string>
    <string name="addItemDescription">New To Do Item</string>

</resources>

 

Code:

        setContentView(R.layout.activity_main);
        //Get Reference of UI widget
        ListView myListView = (ListView)findViewById(R.id.myListView);
        final EditText myEditText = (EditText)findViewById(R.id.myEditText);
        final ArrayList<String> todoItems = new ArrayList<String>();
        //Create ArrayAdapter to bind to ListView
        final ArrayAdapter<String> aa;
        aa = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1,
                todoItems);
        //Bind ArrayAdapter to ListView
        myListView.setAdapter(aa);
        myEditText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(event.getAction() == KeyEvent.ACTION_DOWN){
                    if(keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                            || keyCode == KeyEvent.KEYCODE_ENTER){
                        todoItems.add(0, myEditText.getText().toString());
                        aa.notifyDataSetChanged();
                        myEditText.setText("");
                    }
                }               
                return false;
            }
        });

 

運行效果:

image

理解Fragment

1. Fragment 介紹

Fragment(碎片)容許將Activity拆分紅多個徹底獨立封裝的可重用的組件,每一個組件有它本身的生命週期和UI佈局。

Fragment最大的優點是你能夠爲不一樣大小屏幕的設備建立動態靈活的UI。

要經過Android支持包來使用Fragment,必需要保證Activity是繼承自FragmentActivity類:

Public class MyActivity extends FragmentActivity

建立Fragment

Resource -----

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.hello.MySkeletonFragment" >

    <!-- TODO: Update blank fragment layout -->

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

Code ------

public class MySkeletonFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater
                .inflate(R.layout.fragment_my_skeleton, container, false);
    }

和Activity 不一樣,Fragment 不須要在manifest.xml 進行註冊。這是由於Fragment只有嵌入到一個Activity時,它纔可以存在,它的生命週期依賴於Activity。

Fragment 的生命週期

image

Fragment 特有的生命週期事件

1.從父Activity中綁定和分離Fragment。

2.建立和銷燬Fragment。與Activity不一樣,Fragment的UI不在Onreate中初始化。

3.建立和銷燬用戶界面。OnCreateView與OnDestroyView。

使用OnCreateView方法來初始化Fragment:填充UI,獲取它所包含的View的引用(綁定到該View的數據),而後建立所需的任何Service和Timer。一旦填充好了View Layout,該View應該從這個處理程序返回:

return Inflater.inflate(R.layout.my_fragment, container, false);

若是Fragment 和 父Activity的UI交互須要一直等到onActivityCreated事件被觸發。

Fragment 狀態

Fragment的命運與它所屬的Activity息息相關。

Fragment Manager 介紹

每個Activity都包含一個Fragment Manager來管理它所包含的Fragment。經過getFragementManager方法得到FragmentManager。

向Activity中添加Fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <EditText
        android:id="@+id/myEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/addItemHint"
        android:contentDescription="@string/addItemDescription"/>
    <ListView
        android:id="@+id/myListView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <fragment android:name="com.example.hello.MyListFragment"
        android:id="@+id/my_list_fragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>
    <fragment android:name="com.example.hello.DetailsFragment"
        android:id="@+id/details_fragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="3"/>
</LinearLayout>

一旦一個Fragment填充後,他就變成一個ViewGroup,會在Activity內顯示和管理它所包含UI。

使用FragmentTransaction

在運行時,用來在一個Activity內添加、刪除和替換Fragment,使得佈局變成動態的。

添加、刪除和替換Fragment

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.ui_Container, new MyListFragment());
        fragmentTransaction.commit();

 

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.beginTransaction()
            .remove(getFragmentManager().findFragmentById(R.id.details_fragment));
        fragmentTransaction.commit();

 

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.details_fragment, new DetailFragment(selected_index));
        fragmentTransaction.commit();

使用FragmentManager 查找Fragment

若是是經過XML佈局的方式把Fragment加入到Activity中的,可使用資源標識符。

getFragmentManager().findFragmentById(R.id.details_fragment);

若是經過FragmentTransaction添加了一個Fragment,應該把容器View的資源標識符指定給想要查找的Fragment;另外,還能夠經過使用findFragmentByTag來查找在FragmentTransaction中指定了Tab標識符的Fragment。

getFragmentManager().findFragmentByTag(arg0);

這種方式在沒有UI的Fragment的Activity中特別有用。由於這樣Fragment並非Acitvity View Layout的一部分,它沒有一個標識符或者一個容器資源標識符,因此它沒法使用findFragmentById()方法找到它。

使用Fragment填充動態的Activity佈局

FragmentManager fm = getFragmentManager();
        DetailsFragment detailsFragment = (DetailsFragment)fm.findFragmentById(R.id.details_container);
        if(detailsFragment == null){
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(R.id.details_container, new DetailsFragment());
            ft.commit();
        }

首先檢查這個UI是否提供以前的狀態已經被提供過了。爲了確保用戶體驗的一致性,當Activity因配置改變而從新啓動時,Android會保存Fragment佈局和back棧。

由於一樣的緣由,當運行時配置改變而建立可替代的佈局時,最好考慮在全部的佈局變化中,包含全部事務所包含的全部的View容器。這樣作的壞處就是FragmentManager把Fragment還原到已不在新佈局中的容器。

在一個給定方向的佈局中刪除一個Fragment容器,只須要將Fragment容器的Visibility屬性設置爲gone便可。

Fragment和Back棧

FragmentManager fm = getFragmentManager();
        DetailsFragment detailsFragment = (DetailsFragment)fm.findFragmentById(R.id.details_container);
        if(detailsFragment == null){
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(R.id.details_container, new DetailsFragment());
            Fragment fragment = getFragmentManager().findFragmentById(R.id.my_list_fragment);
            ft.remove(fragment);
            String tag = null;
            ft.addToBackStack(tag);

            ft.commit();
        }

使FragmentTransaction動起來

想要應用衆多默認過分動畫中一個,能夠對任何FragmentTransaction使用setTransition方法,並傳入一個FragmentTransaction.TRASITION_FRAGMENT_*常量:

ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);

也可使用setCustomAnimations方法對FragmentTransaction應用自定義動畫。

ft.setCustomAnimations(R.animator.slide_in_left, R.animator.slide_in_right);

Fragment和Activity之間的接口

儘管Fragment能夠直接使用主Activity的FragmentManager進行通訊,但一般考慮最好使用Activity作媒介,這樣儘量的讓Fragment獨立和鬆耦合。

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnFragmentInteractionListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

 

public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        public void onFragmentInteraction(Uri uri);
    }

沒有用戶界面的Fragment

大部分狀況下Fragment是封裝了UI的模塊化組件,可是也能夠構建沒有UI的Fragment,該行爲能夠一直持續到Activity的從新啓動。

這特別適合於:按期和UI交互的後臺任務;因配置改變而致使的Activity從新啓動時,保存狀態變得特別重要的場合。

當Activity從新啓動時,同一個Fragment的實例會被保留下來,而不是和它的父Activity一塊兒被銷燬和從新建立。雖然能夠對存在UI的Fragment使用這項技術,可是並不建議這樣作,更好的選擇是把關聯的後臺任務和必要的狀態移入到新的沒有UI的Fragment中,根據須要讓兩個Fragment交互。

FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.ui_container, new MyFirstFragment());
        ft.add(new MyNoUIFragment(), My_No_UI_Fragment);
        MyNoUIFragment mn = (MyNoUIFragment)fm.findFragmentByTag(My_No_UI_Fragment);
        ft.commit();

Android Fragment  類

DialogFragment ---

ListFragment ---

WebViewFragment ---

對To-Do-List 示例使用Fragment

(1)首先,在res/layout文件夾中建立一個新的佈局文件new_item_fragment.xml

 

<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/myEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/addItemHint"
        android:contentDescription="@string/addItemHint" />

 

(2)爲fragment UI建立新的Fragment:NewItemFragment

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.new_item_fragment, container, false);

        return view;
    }

 

(3)定義接口監聽新事項的添加

public interface OnNewItemAddedListner{
        public void onNewItemAdded(String newItem);
    }

 

(4)建立一個變量來保存實現了接口OnNewItemAddedListner的類的引用

private OnNewItemAddedListner onNewItemAddedListner;

@Override
    public void onAttach(Activity activity){
        super.onAttach(activity);
        try{
            onNewItemAddedListner = (OnNewItemAddedListner)activity;
        } catch(ClassCastException e){
            throw new ClassCastException(activity.toString() + "must implement OnNewItemAddedListner");
        }
    }

(5)在NewItemFragment中添加onClickListner事件。當用戶添加新項,不是直接向數組中添加文本,而是把它傳遞到父Activity的OnNewItemAddedListner.onNewItemAdded實現中。

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.new_item_fragment, container, false);
        final EditText myEditText = (EditText)view.findViewById(R.id.myEditText);
        myEditText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(event.getAction() == KeyEvent.ACTION_DOWN){
                    if(keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER){
                        onNewItemAddedListner.onNewItemAdded(myEditText.getText().toString());
                        myEditText.setText("");
                        return true;
                    }
                }
                return false;
            }
        });
        return view;
    }

 

(6)建立to-do事項列表Fragment

package com.example.todolist;

import android.app.Fragment;
import android.app.ListFragment;

/**
* A simple {@link Fragment} subclass.
*
*/
public class ToDoListFragment extends ListFragment {

}

 

(7)從新設計MainActivity的XML佈局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.todolist.MainActivity" >

    <fragment android:name="com.example.todolist.NewItemFragment"
        android:id="@+id/newItemFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/addItemHint"
        android:contentDescription="@string/addItemHint" />
    <fragment android:name="com.example.todolist.ToDoListFragment"
        android:id="@+id/toDoListFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

 

(8)修改MainActivity.java

public class MainActivity extends Activity implements NewItemFragment.OnNewItemAddedListner {

    private ArrayList<String> todoItems;
    private ArrayAdapter<String> aa;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //獲取Fragment的引用
        FragmentManager fm = getFragmentManager();
        ToDoListFragment todoListFragment = (ToDoListFragment)fm.findFragmentById(R.id.toDoListFragment);
        todoItems = new ArrayList<String>();

        aa = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, todoItems);
        todoListFragment.setListAdapter(aa);
    }

 

//前面定義接口的具體實現

    @Override
    public void onNewItemAdded(String newItem) {
        todoItems.add(newItem);
        aa.notifyDataSetChanged();
    }
}

 

Android widget 工具箱

  • TextView
  • EditText
  • Chronometer 一個TextView的擴展,實現一個簡單的計時器
  • ListView 顯示一個對象數組的toString值,其中每個條目都是一個TextView.
  • Spinner 一個組合控件,由一個TextView和一個ListView組成。
  • Button
  • ToggleButton 兩種狀態的按鈕
  • ImageButton
  • RadioButton
  • ViewFlipper 容許將一組View定義爲一個水平行的ViewGroup,其中任意時刻只有一個View可見,而且可見View之間切換以動畫形式表現出來。
  • VideoView
  • QuickContactBadge 與聯繫人相關信息有關,可選擇聯繫人信息
  • ViewPager  能夠水平滾動的UI,經過點擊或者左右拖拽的方式在不一樣View之間切換。
建立分辨率無關的用戶界面

修改現有的視圖

修改to-do-list,先看效果圖

image

下面咱們一步一步來作:

(1)由於ListView 每個條目實際上是TextView,那麼只須要改變每個條目就能影響ListView。

建立一個新的擴展了TextView類ToDoListItem類。重寫onDraw方法。

public class ToDoListItemView extends TextView {
    private Paint marginPaint;
    private Paint linePaint;
    private int pagerColor;
    private float margin;
    public ToDoListItemView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    public ToDoListItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public ToDoListItemView(Context context) {
        super(context);
        init();
    }

    private void init() {
    }

    @Override
    public void onDraw(Canvas canvas){

    }
}

 

(2)建立資源文件:res/values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="notepad_paper">#EEF8E0A0</color>
    <color name="notepad_lines">#FF0000FF</color>
    <color name="notepad_margin">#90FF0000</color>
    <color name="notepad_text">#AA0000FF</color>
</resources>

 

(3) 建立dimens.xml文件,爲邊緣和寬度添加新值

<dimen name="notepad_margin">30dp</dimen>

(4)經過上面的資源文件就能夠繪製新的控件,首先建立相關Paint對象的初始化

private Paint marginPaint;
    private Paint linePaint;
    private int pagerColor;
    private float margin;

private void init() {
        Resources myResources = getResources();
        //建立onDraw中使用的畫刷
        marginPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        marginPaint.setColor(myResources.getColor(R.color.notepad_margin));
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(myResources.getColor(R.color.notepad_lines));
        //得到頁面背景和邊緣寬度
        pagerColor = myResources.getColor(R.color.notepad_paper);
        margin = myResources.getDimension(R.dimen.notepad_margin);
    }

 

(5)重寫onDraw方法

@Override
    public void onDraw(Canvas canvas){
        //繪製頁面的顏色
        canvas.drawColor(pagerColor);
        //繪製邊緣
        canvas.drawLine(0, 0, 0, getMeasuredHeight(), linePaint);
        canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), linePaint);
        //繪製margin
        canvas.drawLine(margin, 0, margin, getMeasuredHeight(), marginPaint);
        //移動文本,讓它跨越邊緣
        canvas.save();
        canvas.translate(margin, 0);
        super.onDraw(canvas);
        canvas.restore();
    }

 

(6)建立一個新的資源res/layout/todolist_item.xml資源來指定每個To-Do List條目

<?xml version="1.0" encoding="utf-8"?>
<com.example.todolist.ToDoListItemView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:scrollbars="vertical"
    android:textColor="@color/notepad_text"
    android:fadingEdge="vertical" />

 

(7) 最後一步在OnCreate中改變ArrayAdapter傳入的參數。使用R.Layout.todolist_item

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //獲取Fragment的引用
        FragmentManager fm = getFragmentManager();
        ToDoListFragment todoListFragment = (ToDoListFragment)fm.findFragmentById(R.id.toDoListFragment);
        todoItems = new ArrayList<String>();

        aa = new ArrayAdapter<String>(this, R.layout.todolist_item, todoItems);
        todoListFragment.setListAdapter(aa);
    }

 

建立複合控件

當建立複合控件時,必須對他包含的視圖的佈局、外觀、交互的定義。複合控件是經過擴展一個ViewGroup(一般是一個佈局)來建立的。所以,要建立一個新的複合控件,首先須要選擇一個最適合放置子控件的佈局類,而後擴展該類。

package com.example.hello;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;

public class MycompoudView extends LinearLayout {

    public MycompoudView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    public MycompoudView(Context context){
        super(context);
    }
}

 

設計複合視圖的UI佈局的首選方法是使用外部資源。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/clearButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Clear" />
</LinearLayout>

 

填充佈局資源,得到控件的引用,掛鉤功能(還未實現)

private EditText editText;
    private Button button;
    public MycompoudView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //使用佈局資源填充視圖
        String infService = Context.LAYOUT_INFLATER_SERVICE;
        LayoutInflater li;
        li = (LayoutInflater)getContext().getSystemService(infService);
        li.inflate(R.layout.clearable_edit_text, this, true);
        //得到對子控件的引用
        editText = (EditText)findViewById(R.id.editText);
        button = (Button)findViewById(R.id.clearButton);
        //掛鉤這個功能
        hookupButton();
    }

使用佈局建立簡單的複合控件

經過建立一個XML資源來封裝要重用的UI模式,之後在建立Activity或Fragment的UI時,能夠把他們的佈局資源定義使用include標籤導入。使用include標籤還可以重寫所包含佈局的根節點的id和layout參數:

<include layout="@layout/clearable_edit_text"
    android:id="@+id/add_new_entry_input"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:layout_gravity="top" />

 

建立定製的視圖

對View 或者 SurfaceView擴展。

(1)建立新的可視界面

(2)定製可訪問性事件

(3)建立一個羅盤視圖的Demo

效果圖:

Screenshot_2014-08-30-17-19-15

製做步驟:

(1)建立新的項目Compass,新增類CompassView擴展自View

public class CompassView extends View {

    public CompassView(Context context) {
        super(context);
        initCompassView();
    }

    public CompassView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initCompassView();
    }

    public CompassView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initCompassView();
    }

}

(2)羅盤應該是一個正圓,並且應該佔據畫布容許的儘量大的空間。所以重寫onMeasure方法來計算最短邊的長度,而後使用這個值並經過setMeasuredDimension來設置高度和寬度。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        //羅盤是一個填充儘量多的空間的圓,經過設置最短的邊界,高度和寬度來設置測量的尺寸
        int measuredWidth = measure(widthMeasureSpec);
        int measureHeight = measure(heightMeasureSpec);
        int d = Math.min(measuredWidth, measureHeight);
        setMeasuredDimension(d, d);
    }

private int measure(int measureSpec) {
        int result = 0;
        // 對測量說明進行編碼
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if(specMode == MeasureSpec.UNSPECIFIED){
            //若是沒有指定邊界,則返回默認大小200
            result = 200;
        }
        else {
            //因爲但願填充可用的空間,因此要返回整個可用的空間的邊界
            result = specSize;
        }
        return result;
    }

(3)修改main_acitivity.xml,使用新的視圖

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.compass.MainActivity" >

    <com.example.compass.CompassView
        android:id="@+id/compassView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

 

(4)修改資源res/values/strings.xml 與 res/values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Compass</string>
    <string name="action_settings">Settings</string>
    <string name="cardinal_north">N</string>
    <string name="cardinal_east">E</string>
    <string name="cardinal_south">S</string>
    <string name="cardinal_west">W</string>

</resources>

 

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <color name="background_color">#F555</color>
    <color name="marker_color">#AFFF</color>
    <color name="text_color">#AFFF</color>
</resources>

(5)CompassView類中添加新的屬性

private float bearing;
    public void setBearing(float _bearing){
        bearing = _bearing;

    }
    public float getBearing(){
        return bearing;
    }

 

(6)完成initCompassView方法,準備繪製羅盤所需的Paint等相關對象

private Paint markerPaint;
private Paint textPaint;
private Paint circlePaint;
private String northString;
private String eastString;
private String southString;
private String westString;
private int textHeight;

private void initCompassView() {
        setFocusable(true);
        Resources r = getResources();
        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setColor(r.getColor(R.color.background_color));
        circlePaint.setStrokeWidth(1);
        circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        northString = r.getString(R.string.cardinal_north);
        eastString = r.getString(R.string.cardinal_east);
        southString = r.getString(R.string.cardinal_south);
        westString = r.getString(R.string.cardinal_west);
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(r.getColor(R.color.text_color));
        textHeight = (int)textPaint.measureText("yY");
        markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        markerPaint.setColor(r.getColor(R.color.marker_color));
    }

(7)開始繪製,見註解

@Override
    protected void onDraw(Canvas canvas){
        //找到控制中心,並將最小邊的長度做爲羅盤的半徑存起來
        int mMeasuredWidth = getMeasuredWidth();
        int mMeasureHeight = getMeasuredHeight();
        int cx = mMeasuredWidth / 2;
        int cy = mMeasureHeight / 2;
        int radius = Math.min(cx, cy);


        //使用drawCircle方法畫出羅盤字盤的邊界,併爲其背景着色
        canvas.drawCircle(cx, cy, radius, circlePaint);


        //這個羅盤是經過旋轉它的字盤來顯示當前的方向的,因此當前的方向老是指向設備的頂部。
        //要實現這一個功能,須要把畫布向與當前方向相反的方向旋轉
        canvas.save();
        canvas.rotate(-bearing, cx, cy);


        //繪製標記了,把畫布旋轉一圈,而且每15度畫一個標記,每45度畫一個方向縮寫
        int textWidth = (int)textPaint.measureText("W");
        int cardinalX = cx - textWidth / 2;
        int cardinalY = cy - radius + textHeight;


        //每15度繪製一個標記,每45度繪製一個文本
        for(int i = 0; i < 24; i++){
            //繪製一個標記
            canvas.drawLine(cx, cy-radius, cx, cy-radius+10, markerPaint);
            canvas.save();
            canvas.translate(0, textHeight);


            //繪製基本方位
            if(i % 6 == 0){
                String dirString = "";
                switch(i){
                    case(0):{
                        dirString = northString;
                        int arrowY = 2*textHeight;
                        canvas.drawLine(cx, arrowY, cx-5, 4*textHeight, markerPaint);
                        canvas.drawLine(cx, arrowY, cx+5, 4*textHeight, markerPaint);
                        canvas.drawLine(cx-5, 4*textHeight, cx, 6*textHeight, markerPaint);
                        canvas.drawLine(cx+5, 4*textHeight, cx, 6*textHeight, markerPaint);
                        break;
                    }
                    case(6):dirString = eastString; break;
                    case(12):dirString = southString; break;
                    case(18):dirString = westString; break;
                }
                canvas.drawText(dirString, cardinalX, cardinalY, textPaint);
            }
            else if(i % 3 == 0){
                //每45度繪製文本
                String angle = String.valueOf(i*15);
                float angleTextWidth = textPaint.measureText(angle);
                int angleTextX = (int)(cx-angleTextWidth/2);
                int angleTextY = cy-radius+textHeight;
                canvas.drawText(angle, angleTextX, angleTextY, textPaint);
            }


            canvas.restore();
            canvas.rotate(15, cx, cy);
        }


        canvas.restore();
    }

(8)添加可訪問性支持。羅盤視圖以可視方式顯示方向,因此爲了提升可訪問性,當方向變化時,須要廣播一個可訪問性事件,說明文本發生了變化。

public void setBearing(float _bearing){
        bearing = _bearing;
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
    }

(9)重寫dispatchPopulateAccessibilityEvent,將當前方向用做可訪問性事件使用的內容值

@SuppressWarnings("deprecation")
    @Override
    public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event){
        super.dispatchPopulateAccessibilityEvent(event);
        if(isShown()){
            String bearingStr = String.valueOf(bearing);
            if(bearingStr.length() > AccessibilityEvent.MAX_TEXT_LENGTH){
                bearingStr = bearingStr.substring(0, AccessibilityEvent.MAX_TEXT_LENGTH);
                event.getText().add(bearingStr);
                return true;
            }
        }
        return false;       
    }

 

Demo到此結束,見開始運行效果。

Adapter簡介

Adapter用來把數據綁定到AdapterView擴展類的視圖組(如ListView、Gallery)。Adapter負責建立表明所綁定父視圖中的底層數據的子視圖。

能夠建立本身的Adapter類,構建本身的由AdapterView派生的控件。

部分原生的Adapter

  • ArrayAdapter 使用數組中每一個對象的toString值來填充和建立視圖。其餘的構造函數容許你使用更復雜的佈局,或者經過擴展類把數據綁定到更復雜的佈局
  • SimpleCursorAdapter 能夠把一個佈局中的視圖和(一般是從Content Provider 查詢返回的)遊標的特定列綁定到一塊兒。能夠指定一個將被填充以顯示每一個子視圖的XML佈局,而後把遊標中的每一列和那個佈局中的特定視圖進行綁定。Adappter將每一個遊標項建立一個新的視圖,並將佈局填充到視圖中,使用遊標中對應列的值填充佈局中的每個視圖。

1. 定製To-Do List ArrayAdapter

Screenshot_2014-08-30-20-28-09

(1) 大多數狀況下使用ArrayAdapter,須要對其進行擴展,並重寫getView方法來佈局視圖分配對象屬性。

建立一個新的ToDoItem類,定義Task和Date屬性,重寫toString

package com.paad.todolist;

import java.text.SimpleDateFormat;
import java.util.Date;

public class ToDoItem {

  String task;
  Date created;

  public String getTask() {
    return task;
  }

  public Date getCreated() {
    return created;
  }

  public ToDoItem(String _task) {
    this(_task, new Date(java.lang.System.currentTimeMillis()));
  }

  public ToDoItem(String _task, Date _created) {
    task = _task;
    created = _created;
  }

  @Override
  public String toString() {
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy");
    String dateString = sdf.format(created);
    return "(" + dateString + ") " + task;
  }
}

 

(2)在ToDoListActivity,修改ArrayList和ArrayAdapter變量類型來存儲ToDoItem對象,而不是字符串。而後須要修改onCreate更新變量的初始化(initialization)

private ArrayList<ToDoItem> todoItems;
  private ToDoItemAdapter aa;

 

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Inflate your view
    setContentView(R.layout.main);
    // Get references to the Fragments
    FragmentManager fm = getFragmentManager();
    ToDoListFragment todoListFragment =
      (ToDoListFragment)fm.findFragmentById(R.id.TodoListFragment);
    // Create the array list of to do items
    todoItems = new ArrayList<ToDoItem>();
    // Create the array adapter to bind the array to the ListView
    int resID = R.layout.todolist_item;
    aa = new ToDoItemAdapter(this, resID, todoItems);
    // Bind the array adapter to the ListView.
    todoListFragment.setListAdapter(aa);
  }

 

(3)更新onNewItemAdded處理程序來支持ToDoItem對象

public void onNewItemAdded(String newItem) {
  ToDoItem newTodoItem = new ToDoItem(newItem);
  todoItems.add(0, newTodoItem);
  aa.notifyDataSetChanged();
}

(4)修改todolist_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TextView
    android:id="@+id/rowDate"
    android:background="@color/notepad_paper"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:scrollbars="vertical"
    android:fadingEdge="vertical"
    android:textColor="#F000"
    android:layout_alignParentRight="true"
  />
  <com.paad.todolist.ToDoListItemView
    android:id="@+id/row"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:scrollbars="vertical"
    android:fadingEdge="vertical"
    android:textColor="@color/notepad_text"
    android:layout_toLeftOf="@+id/rowDate"
  />
</RelativeLayout>

 

(5)ArrayAdapter 的擴展類ToDoItemAdapter,專門用於ToDoItem

package com.paad.todolist;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ToDoItemAdapter extends ArrayAdapter<ToDoItem> {

  int resource;

  public ToDoItemAdapter(Context context,
                         int resource,
                         List<ToDoItem> items) {
    super(context, resource, items);
    this.resource = resource;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    LinearLayout todoView;

    ToDoItem item = getItem(position);

    String taskString = item.getTask();
    Date createdDate = item.getCreated();
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy");
    String dateString = sdf.format(createdDate);

    if (convertView == null) {
      todoView = new LinearLayout(getContext());
      String inflater = Context.LAYOUT_INFLATER_SERVICE;
      LayoutInflater li;
      li = (LayoutInflater)getContext().getSystemService(inflater);
      li.inflate(resource, todoView, true);
    } else {
      todoView = (LinearLayout) convertView;
    }

    TextView dateView = (TextView)todoView.findViewById(R.id.rowDate);
    TextView taskView = (TextView)todoView.findViewById(R.id.row);

    dateView.setText(dateString);
    taskView.setText(taskString);

    return todoView;
  }
}

 

(6)回到ToDoListActivity,使用ToDoItemAdapter來代替ArrayAdapter聲明

private ArrayList<ToDoItem> todoItems;

 

(7)onCreate內,使用新的ToDoItemAdapter來代替ArrayAdapter<ToDoItem>實例化

aa = new ToDoItemAdapter(this, resID, todoItems);

 

到此完成。

 

2. SimpleCursorAdapter 的使用跟Content Provider,遊標和遊標加載器關係密切,這塊只能留到後面學習了。

相關文章
相關標籤/搜索