android一些認識

一.View及其子類

1.view類

view類繼承至 Object 實現了 Drawable.Callback KeyEvent.Callback AccessibilityEventSource接口.

直接子類有:AnalogClock, ImageView, KeyboardView, MediaRouteButton, ProgressBar, Space, SurfaceView, TextView, TextureView, ViewGroup, ViewStub

間接子類無數:AbsListView, AbsSeekBar, AbsSpinner, AbsoluteLayout, ActionMenuView, AdapterView<T extends Adapter>, AdapterViewAnimator, AdapterViewFlipper...


2.view是所有交互式組件的基類.常見View我將它分爲三種:

a.普通視圖->可見組件:如Button,ImageView這種可以直接看見的,形狀確定的靜態試圖.findviewbyid(R.id.)可以在一個root視圖或容器視圖中,查找並返回一個組件視圖活子容器視圖.

b.視圖容器:不可以直接看見的,它提供試圖的佈局及其顯示效果等.這些視圖以viewgroup爲基類.viewgroup是view的子類,是所有容器視圖的基類,如所有佈局視圖, ShadowOverlayContainer,Toolbar,ViewPager,ListView...

((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflater.inflate(R.layout.main, null)通常可以通過xml文件建立一個佈局視圖root對象.

c.普通視圖+視圖容器=AdapterView:既有可見視圖,又有可見視圖的佈局.這些視圖以AdapterView爲基類.AdapterView是viewgroup的子類,是所有需要一個適配器adapter的試圖的基類,如AdapterViewFlipper, AppCompatSpinner, ExpandableListView, Gallery, GridView, ListView, Spinner, StackView...;Adapter沒有基類及接口實現,是所有適配器的基類.與AdapterView配合使用,Adapter提供數據到Item內部的佈局的映射關係,控制單個Item;AdapterView提供Item之間的佈局呈現.子類有BaseAdapter,ArrayAdapter<T>,CursorAdapter,HeaderViewListAdapter,ListAdapter,ResourceCursorAdapter,SimpleAdapter...

Viewxxx一般是視圖容器,xxxView一般是可見試圖.  而gridview,listview這種既是容器又是可見視圖.


3.MVC編程思想

在android圖形化界面中,大量的使用了多線程的編程方式.視圖是用來呈現數據的,可以理解爲數據的後臺線程.那麼如何將數據呈現在view上,我們就需要在修改數據之後,通過視圖數據發生了改變.一般的view使用setText,setcolor等接口攜帶數據的同時就可以通知視圖數據發生了改變;但是容器視圖的數據改變與通知數據改變是分離的,要想該變視圖的一切東西,都需要通過改變適配器指向的數據來完成,然後使用notifyDataSetChanged通知數據發生了改變. 而數據的改變通常需要接受外部的刺激來完成,比如按鈕點擊,滑動,接收到socket等.


二.AdapterView及Adapter  參考:http://blog.csdn.net/lizzywu/article/details/17612789

1.AdapterView

目的:測量各個Item的總佈局寬高,提供所有Item之間的佈局及事件監控.

1.1常見子類AdapterView:ListView,Spinner下拉列表,GridView網格圖,Gallery縮略圖已經被水平的ScrollView和ViewPicker取代,但也還算常用,是一個可以把子項以中心鎖定,水平滾動的列表

1.2常見事件

◆點擊事件:爲列表加載setOnItemClieckListener監聽,重寫onItemClick(發生單擊事件的列表對象ListView,被單擊Item的rootview,在列表中的位置position,被單擊列表項的行ID)方法。

◆長按事件:爲列表加載setOnItemLongClieckListener監聽,重寫onItemLongClick(發生單擊事件的列表對象ListView,被單擊Item的rootview,在列表中的位置position,被單擊列表項的行ID)方法。

◆重要的方法:onmeasure:和子視圖的onmeasure一起決定了itemview循環緩衝區的大小,決定了能同時創建和保存的itemview的個數.所以也可以知道onmeasure的大小跟屏幕的大小頁沒有多大關係.

第二個參數通常是Item的佈局視圖對象.注意:這裏設置的監聽器是該Item的監聽器,而不是該Item裏面的控件的監聽器. 

◆onmeasure的3種模式爲:MeasureSpec.EXACTLY 精確模式,即父視圖希望子視圖直接使用推薦的寬高,或子視圖告知父視圖需要使用的精確寬高;MeasureSpec.AT_MOST 父視圖向子視圖推薦最多使用的寬高,常見於layout_wrapcontent時的推薦高度,或子視圖告知父視圖最多使用的寬高,實際寬高由佈局使用的layout_wrapcontent決定;MeasureSpec.UNSPECIFIED 可以隨意指定視圖的大小. 對於這3種模式都是根據父子視圖的佈局文件來自動選擇模式的.重載onmeasure時,可以手動指定模式.

2.Adapter

目的:根據列表數據,生成使用數據特例化的當前在屏幕上可以顯示的那些Item的根視圖,並通過getchildat來返回這些已經創建Itemview(通常是佈局視圖,當然也可以是被數據特例的其他視圖),在getview中通過向上轉型的方式返回view.注意:如果Adapter管理100個數據,但是當前只有50個數據能在onmeasure的範圍內(該範圍與屏幕大小無關,該範圍決定了需要循環緩存區中子對象的個數)顯示(假如爲第40-90個數據)能在onmeasure的範圍內呈現(也許這50個數據不能完全在屏幕上呈現),那麼只有第40-90個item的視圖對象是被創建,並被Adapter管理着的.沒被創建的對象通過Adapter.getchildat()將返回null對象.在屏幕上不可見的視圖對象,在代碼裏面是也不一定不能訪問該view對象,關鍵是該view子對象是否能被包含在其父對象視圖或視圖容器的onmeasure()方法決定範圍內,這當然也與onmeasure的三種模式有關.

1.1常見子類

(1)ArrayAdapter:他只能處理列表項內容全是文本的情況。

       ◆數據源:數組或者List<String>對象或者其他

(2)SimpleAdapter: 他不僅可以處理列表項全是文本的情況,當列表項中還有其他控件時,同樣可以處理。

       ◆數據源:只能爲List<Map<“鍵”,“值”>>形式的數據

(3)自定義Adapter:根據xml文件中定義的樣式驚醒列表項的填充,適用性最強。

(4)SimpleCursorAdapter:專門用於把遊標中的數據映像到列表中(我們以後再來研究)

1.2自定義Adapter

(1)創建類,繼承自BaseAdapter,或者BaseExpandableListAdapter

(2)重寫其中的四個方法

①int getCount():返回的是數據源對象的個數,即列表項數

②Object getItem(int position):返回指定位置position上的列表

③long getItemId(int position):返回指定位置處的行ID

④View getView(int position, View convertView, ViewGroup parent):返回列表項對應的root視圖,方法體中

◆實例化視圖填充器

◆用視圖填充器,根據Xml文件,實例化視圖

◆根據佈局找到控件,並設置屬性

◆返回Item的特定View視圖,從而可以呈現在position位置的數據

◆調用時機:該item能被包含在onmeasure的範圍內時,將被創建,並添加到保存itemview的循環緩衝區,超出onmeasure範圍的itemview視圖對象將被移除,listview,gridview都自帶回收機制.

關於第二個參數:convertView :The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).是對前一次position位置創建的View的複用,我想是提高效率減少垃圾回收的一種方式.

LayoutInflater.from(context).inflate(R.layout.expandlistview_childgridviewitem, null);可以根據一個佈局文件生成一個View視圖,從而應用於adapterview的Item.

下面是一個expandablegridview的適配器:

                class MyAdapter extends BaseExpandableListAdapter {

            //得到子item需要關聯的數據

            @Override

            public Object getChild(int groupPosition, int childPosition) {

                String key = Caseproperty.get(groupPosition);

                return (Caseproperty_Child.get(key).get(childPosition));

            }


            //得到子item的ID

            @Override

            public long getChildId(int groupPosition, int childPosition) {

                return childPosition;

            }


            @Override

            public View getChildView(int groupPosition, int childPosition,

                                     boolean isLastChild, View convertView, ViewGroup parent) {

                if (convertView == null) {

                    LayoutInflater inflater = (LayoutInflater) SelectCaseSet.this

                            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

                    convertView = inflater.inflate(R.layout.expandlistview_childgridview, null);

                }

                GridView gridView = (GridView) convertView .findViewById(R.id.gridView);


                String key = Caseproperty.get(groupPosition);

                ArrayAdapter<String> adapter=new ArrayAdapter<String>(SelectCaseSet.this,R.layout.expandlistview_childgridviewitem,R.id.child_textview,Caseproperty_Child.get(key));

                gridView.setAdapter(adapter);


                return convertView;

            }

            //獲取當前父item下的子item的個數

            @Override

            public int getChildrenCount(int groupPosition) {

                return 1;

            }

            //獲取當前父item的數據

            @Override

            public Object getGroup(int groupPosition) {

                return Caseproperty.get(groupPosition);

            }


            @Override

            public int getGroupCount() {

                return Caseproperty.size();

            }


            @Override

            public long getGroupId(int groupPosition) {

                return groupPosition;

            }

            //設置父item組件

            @Override

            public View getGroupView(int groupPosition, boolean isExpanded,

                                     View convertView, ViewGroup parent) {

                if (convertView == null) {

                    LayoutInflater inflater = (LayoutInflater) SelectCaseSet.this

                            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

                    convertView = inflater.inflate(R.layout.expandlistview_parentitem,null);

                }

                TextView tv = (TextView) convertView

                        .findViewById(R.id.parent_textview);

                tv.setText(SelectCaseSet.this.Caseproperty.get(groupPosition));

                return tv;

            }

            @Override

            public boolean hasStableIds() {

                return true;

            }


            @Override

            public boolean isChildSelectable(int groupPosition, int childPosition) {

                return true;

            }


        }

    }


3.數據填充

3.1:聲明AdapterView對象,根據ID利用findViewById方法找到此對象

3.2:聲明Adapter對象,根據構造方法實例化此對象。具體如下:

(1)ArrayAdapter<數據類型> adapter = new ArrayAdapter<數據類型>(context:一般指當前Activity對象,layout:每個列表項顯示的佈局,data:數據源變量);

(2)SimpleAdapter adapter = new SimpleAdapter(context:一般指當前Activity對象,data:數據源變量,layout:每個列表項顯示的佈局,new String[]{}:數據源中的“鍵”,new int[]{}:顯示數據源的控件ID);

(3)自定義Adapter類 adapter = new 自定義Adapter類構造方法;

3.3:綁定Adapter對象到Adapter上:AdapterView對象.setAdapter(Adapter對象);

4.動態數據增,插,刪,排序

Adapter可以進行動態的數據變更,使用add(), insert(), remove(), sort(),要求保存數據的容器具有該4中方法(數組array是不可以的比如String[8],list可以);否則系統會報告Java.lang.NullPointerException。能夠進行動態調整的前提是數據源可伸縮的,即size可以調整.

List<String> planents = new ArrayList<String>(); //採用ArrayList的可伸縮大小的數據格式所謂數據源 

initPlanets( planets) ; //對List的數據源進行初始化設置

adapter= new ArrayAdapter<String>(this,  android.R.layout.simple_list_item_1, planents);

this.setListAdapter(adapter); 

…… 

...... 

adapter.add("Pulto"); 

adapter.notifyDataSetChanged(); //在此例子中,無需加上這句也能生效,但仍建議加上,以確保同步;這樣listview就會重新佈局了.


注意數據的改變是指adapter綁定的數據鏈表引用指向的內存空間的數據的變化,可以以任何方式改變該數據,而不一定使用adapter的增,插,刪,排序接口,最後需要調用adapter.notifyDataSetChanged()通知其父窗口重新佈局.adapter.notifyDataSetChanged()該函數神奇的地方是:它對屏幕上其他控件的佈局也會產生映像,即調用之後將重新佈局整個屏幕.

延伸到任何容器視圖如佈局等,在刪除了他們的任意位置的某個子控件後,整個屏幕都將會被重新佈局.不管是否是在該容器視圖內部或者外部的控件或其他佈局,都會被重新計算佈局位置.

三.多個expandable的控件嵌套

1.當多個可展開的VIEW嵌套時,通常出現問題:內部的可展開控件(如gridview,listview等)通常只能同時顯示1-2行.

比如容器視圖ScrollView中嵌套多個gridview或listview時,當滿屏時,每個gridview通常只能顯示一行,然後在各自的空間中可滾動顯示.這顯然不滿足我們的要求.

可以重新gridview的onMeasure,設置爲儘可能高

public class MyGridView extends GridView{

    public MyGridView(android.content.Context context,

                      android.util.AttributeSet attrs)

    {

        super(context, attrs);

    }


    /**

     * 設置不滾動,高度完全展開

     */

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)//傳入的是父佈局給GRIDVIEW推薦使用的寬高,這可能是不能滿足被嵌入組件的需求

    {

        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,   //AT_MOST模式,最大使用Integer.MAX_VALUE/4的高度佈局,意思是"儘可能高"

                MeasureSpec.AT_MOST); //有3中寬高指定模式,expandSpec的最高2爲表示模式, MeasureSpec.makeMeasureSpec函數正是做了這樣的處理.

        super.onMeasure(widthMeasureSpec, expandSpec);                         //最終被嵌入的控件使用的總的佈局寬高 


    }

}

root組件會給其嵌入組件推薦佈局寬高,但最終是否使用這個寬高還是由view組件的onmeasure方法自己決定的. 也就是說view組件自己的寬高可以有自己最終決定.

root組件推薦的寬高與所有嵌入組件的寬高設置及root組件的佈局方式有關係.

2.動態的增加/刪除佈局文件中的控件,可以達到"expandable可展開"的效果.

一個activity只能同時設置一個佈局文件顯示在屏幕上,但是可以建多個佈局文件,將這些佈局文件通過填充器轉換爲代碼中的視圖對象,xml返回的視圖對象就是xml的根節點對象. 通過佈局文件生成的對象對主xml進行更改.

setContentView(R.layout.activity_main);

LinearLayout  t1=((LinearLayout)(findViewById(R.id.lay)));

t1.removeViewAt(2);  //當調用該函數時,立即進行重新佈局,並顯示在屏幕上.刪除第2個控件,將映像其後控件的佈局位置. 

注意又是組件height和weight的屬性需要設置爲:android:layout_height="wrap_content"

android佈局文件中的include語法:包含另一個xml文件<include layout="@layout/content_main"/>

下面是一個完整的xml文件

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

<android.support.design.widget.CoordinatorLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true"

    tools:context="com.example.mapa.myapplication.MainActivity">


    <android.support.design.widget.AppBarLayout

        android:layout_height="wrap_content"

        android:layout_width="match_parent"

        android:theme="@style/AppTheme.AppBarOverlay">


        <android.support.v7.widget.Toolbar

            android:id="@+id/toolbar"

            android:layout_width="match_parent"

            android:layout_height="?attr/actionBarSize"

            android:background="?attr/colorPrimary"

            app:popupTheme="@style/AppTheme.PopupOverlay" />


    </android.support.design.widget.AppBarLayout>


    <include layout="@layout/content_main"/>               <!--incldue 語法-->

    <TextView

        android:text="Hello World!  5"

        android:id="@+id/t4"

        android:onClick="onclick"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

    <android.support.design.widget.FloatingActionButton

        android:id="@+id/fab"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="bottom|end"

        android:layout_margin="@dimen/fab_margin"

        android:src="@android:drawable/ic_dialog_email" />


</android.support.design.widget.CoordinatorLayout>


四.視圖控件或視圖容器在屏幕上佈局時所佔用屏幕的寬高

1.容器視圖中的控件的onmeasure方法和ondraw方法的調用順序:向調用所有被嵌入控件的onmeasure方法,然後調用外部容器的onmeasure方法;然後調用被嵌入控件的ondraw方法,容器的ondraw方法不會被調用.子控件在ondraw被調用的順序與容器的佈局方式有關,linearlayout當然是依次被調用,而relativelayout就不是了,當控件的位置在屏幕之外的控件的ondraw是不會被調用的;根據佈局方式的不同onmeasure在一次顯示中可能被多次調用,而ondraw最多被調用一次.再來數理一下:以線性佈局爲例,依次調用第一個容器中子控件的onmeasure,然後調用第一個容器的onmeasure;第二個容器...;一次調用能在當前屏幕範圍內顯示的所有控件的ondraw將控件呈現在屏幕上.


2.視圖控件或視圖容器在屏幕上佈局時所佔用屏幕的寬高由view的虛方法onmeasure方法決定.

視圖容器會根據自己的佈局參數(比如xml中的layout自己的參數)首先推薦給其子控件一個佈局寬高,然後子控件給其返回一個佈局寬高,作爲自己最終需要顯示的寬高,父容器使用該寬高來計算自己的寬高.

只有一個xml中的根容器的寬高計算出來之後,才能根據屏幕的尺度決定哪些控件的ondraw會被調用,位置在屏幕外的控件的ondraw是不會被調用的.

3.可展開容器嵌套:外層容器向內層容器和組件推薦佈局寬高,內層容器和組件根據這個推薦的佈局寬高和自己的佈局參數,返回給外層容器自己實際需要的佈局寬高,外層容器根據自己的佈局參數和這些寬高決定總的佈局寬高.

4.view是什麼?

在Android的官方文檔中是這樣描述的:這個類表示了用戶界面的基本構建模塊。一個View佔用了屏幕上的一個矩形區域並且負責界面繪製和事件處理。View是用來構建用戶界面組件(Button,Textfields等等)的基類。ViewGroup子類是各種佈局的基類,它是個包含其他View(或其他ViewGroups)和定義這些View佈局參數的容器。

內存中的view:同一個窗口的所用view 都存儲在一個樹內,既可以通過代碼動態增加刪除view, 也可以通過在xml文件中定義一個view樹來構造這個樹.

屏幕上的view:view是一個矩形區域,使用左&上的座標以及長和寬可以表示一個View. 我們可以使用方法getLeft() getTop() getRight() getBottom() getWidth() 等函數來獲取其位置信息.

5.view的重繪過程

view在屏幕顯示的過程在內存中體現爲View樹的遞歸繪製過程,從ViewGroup一直向下遍歷,直到所有的子view都完成繪製,那這一切的源頭在什麼地方(是誰最發起measure、layout和draw的)?當然就是在View樹的源頭了——ViewRoot,ViewRoot中包含了窗口的總容器DecorView,ViewRoot中的performTraversal()方法會依次調用decorView的measure、layout、draw方法,從而完成view樹的繪製。invalidate()方法會導致整個View樹的重新繪製,而且view中的狀態標誌mPrivateFlags中有一個關於當前視圖是否需要重繪的標誌位DRAWN,也就是說只有標誌位DRAWN置位的視圖才需要進行重繪。當視圖調用invalidate()方法時,首先會將當前視圖的DRAWN標誌置位,之後有一個循環調用parent.invalidateChildinParent(),這樣會導致從當前視圖依次向上遍歷直到根視圖ViewRoot,這個過程會將需要重繪的視圖標記DRAWN置位,之後ViewRoot調用performTraversals()方法,完成視圖的繪製過程。

5.自定義一個view

基本上需要實現3個方法onMeasure()、onLayout()、onDraw()三個子方法。具體操作如下:

1、measure操作

measure操作主要用於計算視圖的大小,即視圖的寬度和長度。在view中定義爲final類型,要求子類不能修改。measure()函數中又會調用下面的函數:

     (1)onMeasure(),視圖大小的將在這裏最終確定,也就是說measure只是對onMeasure的一個包裝,子類可以覆寫onMeasure()方法實現自己的計算視圖大小的方式,並通過setMeasuredDimension(width, height)保存計算結果。

2、layout操作

     layout操作用於設置視圖在屏幕中顯示的位置。在view中定義爲final類型,要求子類不能修改。layout()函數中有兩個基本操作:

     (1)setFrame(l,t,r,b),l,t,r,b即子視圖在父視圖中的具體位置,該函數用於將這些參數保存起來;

     (2)onLayout(),在View中這個函數什麼都不會做,提供該函數主要是爲viewGroup類型佈局子視圖用的;

3、draw操作

     draw操作利用前兩部得到的參數,將視圖顯示在屏幕上,到這裏也就完成了整個的視圖繪製工作。子類也不應該修改該方法,因爲其內部定義了繪圖的基本操作:

     (1)繪製背景;

     (2)如果要視圖顯示漸變框,這裏會做一些準備工作;

     (3)繪製視圖本身,即調用onDraw()函數。在view中onDraw()是個空函數,也就是說具體的視圖都要覆寫該函數來實現自己的顯示(比如TextView在這裏實現了繪製文字的過程)。而對於ViewGroup則不需要實現該函數,因爲作爲容器是“沒有內容“的,其包含了多個子view,而子View已經實現了自己的繪製方法,因此只需要告訴子view繪製自己就可以了,也就是下面的dispatchDraw()方法;

     (4)繪製子視圖,即dispatchDraw()函數。在view中這是個空函數,具體的視圖不需要實現該方法,它是專門爲容器類準備的,也就是容器類必須實現該方法;

     (5)如果需要(應用程序調用了setVerticalFadingEdge或者setHorizontalFadingEdge),開始繪製漸變框;

     (6)繪製滾動條;

      從上面可以看出自定義View需要最少覆寫onMeasure()和onDraw()兩個方法。


五.Fragment

1.Fragment的生命週期

Fragment必須是依存與Activity而存在的,因此Activity的生命週期會直接影響到Fragment的生命週期。

Fragment的幾個重要的生命週期函數:

onAttach(Activity):當Fragment與Activity發生關聯時調用。

onCreateView(LayoutInflater, ViewGroup,Bundle):創建該Fragment的視圖

onActivityCreated(Bundle):當Activity的onCreate方法返回時調用

onDestoryView():與onCreateView想對應,當該Fragment的視圖被移除時調用

onDetach():與onAttach相對應,當Fragment與Activity關聯被取消時調用

2.Fragment的使用方式:2種

2.1靜態的使用Fragment:把Fragment當成普通的控件,直接寫在Activity的佈局文件中,這需要重新實現一個屬於自己的Fragment,在其生命週期中進行佈局及相應設置.同實現Activity的方式一樣的.

步驟:

a、繼承Fragment,重寫onCreateView決定Fragemnt的佈局

MyFragment的佈局文件:myfragment.xml

    <?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="match_parent"  

        android:orientation="vertical" >  

      

        <TextView  

            android:layout_width="fill_parent"  

            android:layout_height="fill_parent"  

            android:gravity="center"  

            android:text="使用Fragment做主面板"  

            android:textSize="20sp"  

            android:textStyle="bold" />  

      

    </LinearLayout> 

 

public class MyFragment extends Fragment  

{  

    private ImageButton mLeftMenu;  

    @Override  

    public View onCreateView(LayoutInflater inflater, ViewGroup container,  

            Bundle savedInstanceState)  

    {  

        View view = inflater.inflate(R.layout.myfragment, container, false);  

        return view;  

    }  

}

b、在Activity中聲明此Fragment,就當和普通的View一樣

Activity的佈局文件:activity_main.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" >  

      

        <com.zte.mata.MyFragment  

            android:id="@+id/id_fragment_title"  

            android:name="com.zhy.zhy_fragments.TitleFragment"  

            android:layout_width="fill_parent"  

            android:layout_height="45dp" />  

      

        <com.zte.mata.MyFragment  

            android:layout_below="@id/id_fragment_title"  

            android:id="@+id/id_fragment_content"  

            android:name="com.zhy.zhy_fragments.ContentFragment"  

            android:layout_width="fill_parent"  

            android:layout_height="fill_parent" />  

      

    </RelativeLayout>  


public class MainActivity extends Activity  

{  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState)  

    {  

        super.onCreate(savedInstanceState);  

        //requestWindowFeature(Window.FEATURE_NO_TITLE);  

        setContentView(R.layout.activity_main);  

    }  

  

}  


2.2動態的使用Fragment:基本方式是使用Fragment視圖對象去替換某個佈局視圖對象中的全部內容.

http://blog.csdn.net/lmj623565791/article/details/37970961


a.Fragment使用上面的,不變:不管動態使用還是靜態使用,特例Fragment的實現是不變的.

b.Activity的佈局文件需要做一些修改

    <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" >  

      

        <fragment  

            android:id="@+id/id_fragment_title"  

            android:name="com.zhy.zhy_fragments.TitleFragment"  

            android:layout_width="fill_parent"  

            android:layout_height="45dp" />  

      

        <include                               //在屏幕的底部添加4個按鈕

            android:id="@+id/id_ly_bottombar"  

            android:layout_width="fill_parent"  

            android:layout_height="55dp"  

            android:layout_alignParentBottom="true"  

            layout="@layout/bottombar" />  

      

        <FrameLayout                          //佔位佈局,爲了使fragment插入到該佈局中.

            android:id="@+id/id_content"  

            android:layout_width="fill_parent"  

            android:layout_height="fill_parent"  

            android:layout_above="@id/id_ly_bottombar"  

            android:layout_below="@id/id_fragment_title" />  

      

    </RelativeLayout>  



public class MainActivity extends Activity implements OnClickListener    //效果類似於微信

{  

    private LinearLayout mTabWeixin;  

    private LinearLayout mTabFriend;  

  

    private MyFragment mWeixin;  

    private MyFragment mFriend;  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState)  

    {  

        super.onCreate(savedInstanceState);  

        requestWindowFeature(Window.FEATURE_NO_TITLE);  

        setContentView(R.layout.activity_main);  

  

        // 初始化控件和聲明事件  

        mTabWeixin = (LinearLayout) findViewById(R.id.tab_bottom_weixin);   //主界面底部的2個按鈕,位於

        mTabFriend = (LinearLayout) findViewById(R.id.tab_bottom_friend);  

        mTabWeixin.setOnClickListener(this);  

        mTabFriend.setOnClickListener(this);  

  

        // 設置默認的Fragment  

        setDefaultFragment();  

    }  

  

    private void setDefaultFragment()  

    {  

        FragmentManager fm = getFragmentManager();  

        FragmentTransaction transaction = fm.beginTransaction();  

        mWeixin = new MyFragment();  

        transaction.replace(R.id.id_content, mWeixin);  

        transaction.commit();  

    }  

  

    @Override  

    public void onClick(View v)     //點擊底部的按鈕,切換fragment

    {  

        FragmentManager fm = getFragmentManager();  

        // 開啓Fragment事務  

        FragmentTransaction transaction = fm.beginTransaction();  

  

        switch (v.getId())  

        {  

        case R.id.tab_bottom_weixin:  

            if (mWeixin == null)  

            {  

                mWeixin = new MyFragment();  

            }  

            // 使用當前Fragment的佈局替代id_content的控件  

            transaction.replace(R.id.id_content, mWeixin);  //R.id.id_content甚至可以是根佈局的id

            break;  

        case R.id.tab_bottom_friend:  

            if (mFriend == null)  

            {  

                mFriend = new MyFragment();  

            }  

            transaction.replace(R.id.id_content, mFriend);  

            break;  

        }  

        // transaction.addToBackStack();  

        // 事務提交  

        transaction.commit();  

    }  

  

}  



注意:常用Fragment的哥們,可能會經常遇到這樣Activity狀態不一致:State loss這樣的錯誤。主要是因爲:commit方法一定要在Activity.onSaveInstance()之前調用。


上述,基本是操作Fragment的所有的方式了,在一個事務開啓到提交可以進行多個的添加、移除、替換等操作。

值得注意的是:如果你喜歡使用Fragment,一定要清楚這些方法,哪個會銷燬視圖,哪個會銷燬實例,哪個僅僅只是隱藏,這樣才能更好的使用它們。

a、比如:我在FragmentA中的EditText填了一些數據,當切換到FragmentB時,如果希望會到A還能看到數據,則適合你的就是hide和show;也就是說,希望保留用戶操作的面板,你可以使用hide和show,當然了不要使勁在那new實例,進行下非null判斷。

b、再比如:我不希望保留用戶操作,你可以使用remove(),然後add();或者使用replace()這個和remove,add是相同的效果。

c、remove和detach有一點細微的區別,在不考慮回退棧的情況下,remove會銷燬整個Fragment實例,而detach則只是銷燬其視圖結構,實例並不會被銷燬。那麼二者怎麼取捨使用呢?如果你的當前Activity一直存在,那麼在不希望保留用戶操作的時候,你可以優先使用detach。


3.管理Fragment回退棧    http://blog.csdn.net/lmj623565791/article/details/37992017

類似與Android系統爲Activity維護一個任務棧,我們也可以通過Activity維護一個回退棧來保存每次Fragment事務發生(commit)的變化。如果你將Fragment任務添加到回退棧,當用戶點擊後退按鈕時,將看到上一次的保存的Fragment。一旦Fragment完全從後退棧中彈出,用戶再次點擊後退鍵,則退出當前Activity。

添加fragment到回退棧的方法:FragmentTransaction.addToBackStack(String)


a、獲取FragmentManage的方式:

getFragmentManager() // v4中,getSupportFragmentManager


b、主要的操作都是FragmentTransaction的方法

FragmentTransaction transaction = fm.benginTransatcion();//開啓一個事務

transaction.add() 往Activity中添加一個Fragment

transaction.remove() 從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧(回退棧後面會詳細說),這個Fragment實例將會被銷燬。

transaction.replace()使用另一個Fragment替換當前的,實際上就是remove()然後add()的合體~

transaction.hide()隱藏當前可見的Fragment,僅僅是設爲不可見,並不會銷燬銷燬view

transaction.show()顯示之前隱藏的Fragment

detach()會將view從UI中移除,和remove()不同,此時fragment的狀態依然由FragmentManager維護。

attach()重建view視圖,附加到UI上並顯示。

transatcion.commit()//提交一個事務


public class FragmentTwo extends Fragment implements OnClickListener  

{  

  

    private Button mBtn ;  

    @Override  

    public View onCreateView(LayoutInflater inflater, ViewGroup container,  

            Bundle savedInstanceState)  

    {  

        View view = inflater.inflate(R.layout.fragment_two, container, false);  

        mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);  

        mBtn.setOnClickListener(this);  

        return view ;   

    }  

    @Override  

    public void onClick(View v)  

    {  

        FragmentThree fThree = new FragmentThree();  

        FragmentManager fm = getFragmentManager();  

        FragmentTransaction tx = fm.beginTransaction();  

        tx.hide(this);   //這比直接添加到回退棧更高效,即不銷燬fragment實例也不銷燬view對象;如果只使用addToBackStack,將銷燬view對象.

        tx.add(R.id.id_content , fThree, "THREE");  

//      tx.replace(R.id.id_content, fThree, "THREE");  

        tx.addToBackStack(null);  

        tx.commit();  

    }



4.Fragment與Activity通信  http://blog.csdn.net/lmj623565791/article/details/37992017

因爲所有的Fragment都是依附於Activity的,所以通信起來並不複雜,大概歸納爲:

a、如果你Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法

b、如果Activity中未保存任何Fragment的引用,那麼沒關係,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然後進行操作。

c、在Fragment中可以通過getActivity得到當前綁定的Activity的實例,然後進行操作。

注:如果在Fragment中需要Context,可以通過調用getActivity(),如果該Context需要在Activity被銷燬後還存在,則使用getActivity().getApplicationContext()


因爲要考慮Fragment的重複使用,所以必須降低Fragment與Activity的耦合,而且Fragment更不應該直接操作別的Fragment,畢竟Fragment操作應該由它的管理者Activity來決定。

所以Fragment之間或與activity通訊的基本思路是:Fragment中聲明一個接口,然後在activity中繼承並實現該接口,fragment通過getactivity方法獲得該接口,並調用相應的方法.


5.Fragment如何處理運行時配置發生變化(多activity實例)

運行時配置發生變化,最常見的就是屏幕發生旋轉.很多人覺得強制設置屏幕的方向就可以了,但是有一點,當你的應用被至於後臺(例如用戶點擊了home),長時間沒有返回的時候,你的應用也會被重新啓動。比如上例:如果你把上面的例子你至於FragmentThree界面(非activity啓動的默認fragment界面),然後處於後臺狀態,長時間後你會發現當你再次通過home打開時,上面FragmentThree與FragmentOne疊加在一起,這就是因爲你的Activity重新啓動,在原來的FragmentThree上又繪製了一個FragmentOne。


  

public class MainActivity extends Activity  

  

{  

    private FragmentOne mFOne;  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState)  

    {  

        super.onCreate(savedInstanceState);  

        requestWindowFeature(Window.FEATURE_NO_TITLE);  

        setContentView(R.layout.activity_main);  

  

        mFOne = new FragmentOne();  

        FragmentManager fm = getFragmentManager();  

        FragmentTransaction tx = fm.beginTransaction();  

        tx.add(R.id.id_content, mFOne, "ONE");  

        tx.commit();  

  

    }  

  

}


public class FragmentOne extends Fragment  

{  

    private static final String TAG = "FragmentOne";  

  

    @Override  

    public View onCreateView(LayoutInflater inflater, ViewGroup container,  

            Bundle savedInstanceState)  

    {  

        Log.e(TAG, "onCreateView");  

        View view = inflater.inflate(R.layout.fragment_one, container, false);  

        return view;  

    }  

  

    @Override  

    public void onCreate(Bundle savedInstanceState)  

    {  

        // TODO Auto-generated method stub  

        super.onCreate(savedInstanceState);  

  

        Log.e(TAG, "onCreate");  

    }  

  

    @Override  

    public void onDestroyView()  

    {  

        // TODO Auto-generated method stub  

        super.onDestroyView();  

        Log.e(TAG, "onDestroyView");  

    }  

  

    @Override  

    public void onDestroy()  

    {  

        // TODO Auto-generated method stub  

        super.onDestroy();  

        Log.e(TAG, "onDestroy");  

    }  

  


很簡單的代碼,當你運行之後,不斷的旋轉屏幕,你會發現每旋轉一次屏幕,屏幕上就多了一個FragmentOne的實例,並且後臺log會打印出許多套生命週期的回調。

    07-20 08:18:46.651: E/FragmentOne(1633): onCreate  

    07-20 08:18:46.651: E/FragmentOne(1633): onCreate  

    07-20 08:18:46.651: E/FragmentOne(1633): onCreate  

    07-20 08:18:46.681: E/FragmentOne(1633): onCreateView  

    07-20 08:18:46.831: E/FragmentOne(1633): onCreateView  

    07-20 08:18:46.891: E/FragmentOne(1633): onCreateView  

也就是說:配置改變之後,fragment會重新創建.

這是爲什麼呢,因爲當屏幕發生旋轉,Activity發生重新啓動,默認的Activity中的Fragment也會跟着Activity重新創建;這樣造成當旋轉的時候,本身存在的Fragment會重新啓動,然後當執行Activity的onCreate時,又會再次實例化一個新的Fragment,這就是出現的原因。

那麼如何解決呢:


其實通過檢查onCreate的參數Bundle savedInstanceState就可以判斷,當前是否發生Activity的重新創建:


默認的savedInstanceState會存儲一些數據,包括Fragment的實例:通過打印可以看出:

    E/FragmentOne(1782): Bundle[{android:fragments=android.app.FragmentManagerState@40d0b7b8, android:viewHierarchyState=Bundle[{android:focusedViewId=2131230721, android:views=android.util.SparseArray@40d0af68}]}]  


所以,我們簡單改一下代碼,只有在savedInstanceState==null時,才進行創建Fragment實例:

public class MainActivity extends Activity  

  

{  

    private static final String TAG = "FragmentOne";  

    private FragmentOne mFOne;  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState)  

    {  

        super.onCreate(savedInstanceState);  

        requestWindowFeature(Window.FEATURE_NO_TITLE);  

        setContentView(R.layout.activity_main);  

  

        Log.e(TAG, savedInstanceState+"");  

          

        if(savedInstanceState == null)  

        {  

            mFOne = new FragmentOne();  

            FragmentManager fm = getFragmentManager();  

            FragmentTransaction tx = fm.beginTransaction();  

            tx.add(R.id.id_content, mFOne, "ONE");  

            tx.commit();  

        }      

    }  

}


現在還存在一個問題,就是重新繪製時,Fragment發生重建,原本的數據如何保持?

其實和Activity類似,Fragment也有onSaveInstanceState的方法,在此方法中進行保存數據,然後在onCreate或者onCreateView或者onActivityCreated進行恢復都可以。


6.Fragmeny與ActionBar和MenuItem集成

Fragment可以添加自己的MenuItem到Activity的ActionBar或者可選菜單中。

a、在Fragment的onCreate中調用 setHasOptionsMenu(true);

b、然後在Fragment子類中實現onCreateOptionsMenu

c、如果希望在Fragment中處理MenuItem的點擊,也可以實現onOptionsItemSelected;當然了Activity也可以直接處理該MenuItem的點擊事件。


public class FragmentOne extends Fragment  

{  

  

    @Override  

    public void onCreate(Bundle savedInstanceState)  

    {  

        super.onCreate(savedInstanceState);  

        setHasOptionsMenu(true);  

    }  

  

    @Override  

    public View onCreateView(LayoutInflater inflater, ViewGroup container,  

            Bundle savedInstanceState)  

    {  

        View view = inflater.inflate(R.layout.fragment_one, container, false);  

        return view;  

    }  

  

    @Override  

    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)  

    {  

        inflater.inflate(R.menu.fragment_menu, menu);  

    }  

  

    @Override  

    public boolean onOptionsItemSelected(MenuItem item)  

    {  

        switch (item.getItemId())  

        {  

        case R.id.id_menu_fra_test:  

            Toast.makeText(getActivity(), "FragmentMenuItem1", Toast.LENGTH_SHORT).show();  

            break;  

        }  

        return true;  

    }  

  

}  


7、沒有佈局的Fragment的作用

沒有佈局文件Fragment實際上是爲了保存,當Activity重啓時,保存大量數據準備的

http://blog.csdn.net/lmj623565791/article/details/37936275

<Android 屏幕旋轉 處理 AsyncTask 和 ProgressDialog 的最佳方案 >

8.使用Fragment創建對話框

這是Google推薦的方式:各種對話框

http://blog.csdn.net/lmj623565791/article/details/37815413



六.View類可以通過setAnimation來設置動畫

最基本的4種動畫以及1個動畫set:

AlphaAnimation   Alpha動畫:淡入淡出動畫

ScaleAnimation   Scale動畫:淡入淡出

RotateAnimation Rotate動畫:旋轉動畫

TranslateAnimation Translate動畫:移動動畫

AnimationSet 動畫集合,用於添加多種動畫


例子:

AnimationSet As=new AnimationSet(true);

AlphaAnimation Aa=new AlphaAnimation(1,0);

Aa.setDuration(500);

As.addAnimation(Aa);

button.setAnimation(As);


七.Android的消息循環機制

Android的每個線程都允許存在一個獨立的消息隊列及異步消息循環處理機制.Activity主線程已經在底層實現了該機制,只需要在該主線程中定義Handler及向該Handler發送消息,主線程的消息處理機制就能將消息分發給Handler進行處理. 當然每個線程都可以構造一個消息處理機制,來處理髮往該線程中的消息,我們自定義的消息一般用一個單獨的線程來處理.主線程一般處理的是UI消息.

7.1任意線程中實現消息循環機制,並向指定線程中發送消息.

主要的類:Handler 、 Looper 、Message 



    class Rub implements Runnable {  

  

        public Handler myHandler;  

        // 線程體  

        @Override  

        public void run() {  

  

            Looper.prepare();             //實例化了一個Looper對象及MessageQueue對象,一個線程只能調用一次

             myHandler = new Handler() {  //在該線程中定義的Handler將綁定該線程中的消息循環隊列

                @Override  

                public void handleMessage(Message msg) {  

                    String ms = "";  

                    if (msg.what == 0x123) {  

                        int data = msg.getData().getInt(DATA);  

                        //循環的時候界面依舊可以點擊next按鈕 這是本實例效果  

                        for (int i = 0; i < data; i++) {  

                            try {  

                                //循環一次 暫停1秒  

                                Thread.sleep(500);  

                            } catch (InterruptedException e) {  

                                e.printStackTrace();  

                            }  

                            ms += String.valueOf(i) + "  ";  

                            Toast.makeText(getApplicationContext(), ms, Toast.LENGTH_LONG)  

                            .show();  

                              

                        }  

                  

                    }  

                }  

  

            };  

            Looper.loop();   //Looper主要作用: 1、 與當前線程綁定,保證一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue。

2、 loop()方法,不斷從MessageQueue中去取消息,交給消息的target屬性的dispatchMessage去處理。

        }  

  

    }


protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

  

        final Rub rub = new Rub();  

        //子線程中不能有UI組件進行操作   

        th = new Thread(rub);  

        // 啓動線程  

        th.start();  

        ok1.setOnClickListener(new OnClickListener() {  

  

            @Override  

            public void onClick(View v) {  

                Message msg = new Message();  

                msg.what = 0x123;  

                Bundle bundle = new Bundle();  

                bundle.putInt(DATA, Integer.parseInt(ed1.getText().toString()));  

                msg.setData(bundle);  

                rub.myHandler.sendMessage(msg);     //在主線成中可以向任意線程發送消息

//rub.myHandler.sendEmptyMessageDelayed(int what, long delayMillis)

//rub.myHandler.sendMessageDelayed(Message msg, long delayMillis)  

//rub.myHandler.post(new Runnable() {//這是直接向消息隊列中傳入一個具有callback方法的空消息,即直接讓LOOPER運行該callback方法,而不會運行handler.handleMessage方法

            @Override  

            public void run()  

            {  

                Log.e("TAG", Thread.currentThread().getName());  

                mTxt.setText("yoxi");  

            }  

        }); 

                  

            }  

        }); 

}


Handler中分發消息的一些方法

post(Runnable)

postAtTime(Runnable,long)

postDelayed(Runnable long)

sendEmptyMessage(int)

sendMessage(Message)

sendMessageAtTime(Message,long)

sendMessageDelayed(Message,long)

總結一下


1、首先Looper.prepare()在本線程中保存一個Looper實例,然後該實例中保存一個MessageQueue對象;因爲Looper.prepare()在一個線程中只能調用一次,所以MessageQueue在一個線程中只會存在一個。

2、Looper.loop()會讓當前線程進入一個無限循環,不斷從MessageQueue的實例中讀取消息,然後回調msg.target.dispatchMessage(msg)方法。

3、Handler的構造方法,會首先得到當前線程中保存的Looper實例,進而與Looper實例中的MessageQueue形成關聯。

4、Handler的sendMessage方法,會給msg的target賦值爲handler自身,然後加入MessageQueue中。

5、在構造Handler實例時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終調用的方法。


好了,總結完成,大家可能還會問,那麼在Activity中,我們並沒有顯示的調用Looper.prepare()和Looper.loop()方法,爲啥Handler可以成功創建呢,這是因爲在Activity的啓動代碼中,已經在當前UI線程調用了Looper.prepare()和Looper.loop()方法。


7.2在Activity的主線程中使用消息隊列

下面是將消息直接發送到主線程的消息隊列,並調用在主線程中定義的Handler來處理該消息.


public class MyHandlerActivity extends Activity { 

    Button button; 

    MyHandler myHandler; 

 

    protected void onCreate(Bundle savedInstanceState) { 

        super.onCreate(savedInstanceState); 

        setContentView(R.layout.handlertest); 

 

        button = (Button) findViewById(R.id.button); 

        myHandler = new MyHandler(); // 當創建一個新的Handler實例時, 它會綁定到當前線程和消息的隊列中,開始分發數據 

                                    // Handler有兩個作用, (1) : 定時執行Message和Runnalbe 對象 

                                    // (2): 讓一個動作,在不同的線程中執行。 

 

        // 它安排消息,用以下方法 

        // post(Runnable) 

        // postAtTime(Runnable,long) 

        // postDelayed(Runnable,long) 

        // sendEmptyMessage(int) 

        // sendMessage(Message); 

        // sendMessageAtTime(Message,long) 

        // sendMessageDelayed(Message,long) 

      

        // 以上方法以 post開頭的允許你處理Runnable對象 

        //sendMessage()允許你處理Message對象(Message裏可以包含數據,) 

 

        MyThread m = new MyThread(); 

        new Thread(m).start(); //該任意線程中向主線程發送消息,並用myHandler處理

    } 

 

    /** 

    * 接受消息,處理消息 ,此Handler會與當前主線程一塊運行 

    * */ 

 

    class MyHandler extends Handler { 

        public MyHandler() { 

        } 

 

        public MyHandler(Looper L) { 

            super(L); 

        } 

 

        // 子類必須重寫此方法,接受數據 

        @Override 

        public void handleMessage(Message msg) { 

            // TODO Auto-generated method stub 

            Log.d("MyHandler", "handleMessage。。。。。。"); 

            super.handleMessage(msg); 

            // 此處可以更新UI 

            Bundle b = msg.getData(); 

            String color = b.getString("color"); 

            MyHandlerActivity.this.button.append(color); 

        } 

    } 

 

    class MyThread implements Runnable { 

        public void run() { 

 

            try { 

                Thread.sleep(10000); 

            } catch (InterruptedException e) { 

                // TODO Auto-generated catch block 

                e.printStackTrace(); 

            } 

 

            Log.d("thread。。。。。。。", "mThread。。。。。。。。"); 

            Message msg = new Message();   //新建一個消息併發送

            Bundle b = new Bundle();// 存放數據 

            b.putString("color", "我的"); 

            msg.setData(b); 

            MyHandlerActivity.this.myHandler.sendMessage(msg); // 向Handler發送消息,更新UI 

        } 

    } 


八.Android的訂閱-消息通知機制

eventbus機制:   http://blog.csdn.net/harvic880925/article/details/40660137

otto機制

Android開發使用的常見第三方框架彙總: http://blog.csdn.net/liuhaomatou/article/details/44857005


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章