1. Android UI 幾個基本概念java
這種封裝使得Fragment特別適合針對不一樣的屏幕尺寸優化UI佈局以及建立可重用的UI元素。android
每一個Fragment都包含本身的UI佈局,並接受相關的輸入事件,可是必須與包含它們的Acitivity緊密綁定在一塊兒,也就是說Fragment必須簽入到Activity。canvas
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
最經常使用的佈局類:模塊化
--------------------------------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>
運行效果:
(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>
(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屬性被用來肯定每一個元素應該在那個方向上延伸。
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>
(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;
}
});
運行效果:
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 的生命週期
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 工具箱
修改現有的視圖
修改to-do-list,先看效果圖
下面咱們一步一步來作:
(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
效果圖:
製做步驟:
(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用來把數據綁定到AdapterView擴展類的視圖組(如ListView、Gallery)。Adapter負責建立表明所綁定父視圖中的底層數據的子視圖。
能夠建立本身的Adapter類,構建本身的由AdapterView派生的控件。
部分原生的Adapter
1. 定製To-Do List ArrayAdapter
(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,遊標和遊標加載器關係密切,這塊只能留到後面學習了。