1. 程式人生 > >Android 中LayoutInflater(佈局載入器)之實戰篇

Android 中LayoutInflater(佈局載入器)之實戰篇

前言

如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。

導航

效果

小紅書動畫

可以看出在滑動時,會出現視覺差效果。

物體散出

可以看出在滑動時,物品會飄出去。

概述

(1)主要目的是通過這個Demo,理解自定義LayoutInflater.Factory的過程。

(2)理解小紅書的第一版引導頁是如何製作出來的。

分析

這個效果屬於視覺差的效果,原理是根據ViewPager的滑動方向,頁面內物理做同向偏移,只要偏移距離大於頁面的偏移,就會產生速度差,那麼就會實現該效果。

實現速度差,我們需要一個滑動的比例係數:

在頁面進入時:

頁面物體的移動距離 = (頁面長度 - 滑動距離) * 滑動係數

在頁面滑出時:

頁面物體的移動距離 = (0 - 滑動距離 ) * 滑動係數

同時考慮第二張Gif上,發現物體Y軸也存在移動,所以也得需要考慮Y軸方向的滑動,整理下:

//進入時:
view.setTranslateX((vpWidth - positionOffsetPixels) * xIn);
view.setTranslateY((vpWidth - positionOffsetPixels) * yIn);

//退出時
view.setTranslateX((0 - positionOffsetPixels) * xOut);
view.setTranslateY((0 - positionOffsetPixels) * yOut);

這樣就可以實現出:

(1)進入該介面時,介面上的物品快速飛進來。

(2)退出該介面時,介面上的物理快速飛出去。

實現思路

對於上述的分析,這裡的實現思路存在兩種:

  1. 自定義View,自定義xIn、yIn、xOut、yOut四個屬性的係數,所有介面上的物體繼承這個自定義View。

  2. 自定義LayoutInflater.Factory在解析時,將這些自定義屬性提取,以Tag方式儲存起來。

優缺點分析

自定義View:

優點:可以對物體做更多層面的擴充套件,這個自定義LayoutInflater.Factory是不具備的。

缺點:由於介面的物體數量過多,在findViewById時需要處理的View元素過多,極大的增加程式碼量。

自定義LayoutInflater.Factory :

優點:可以在解析過程中對View做統一操作,當出現大量的View時,能夠縮減大量程式碼。

缺點:在解析時預處理View,但是就不能動態的改變View的屬性,要對View進行擴充套件性操作,自定義LayoutInflater.Factory不具備這樣的功能。

自定義LayoutInflater.Factory

上述的兩種方案的優缺點已經分析完畢,但是本文作為實戰篇,所以只會介紹自定義LayoutInflater.Factory這種方式。

在實際場景中,需要結合自身情況,以及上述的優缺點,進行合理選擇。

在介紹之前,先看一段程式碼:

            View view;
            //如果Factory2存在,就會呼叫其onCreateView方法
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
                //如果Factory存在,就會呼叫其onCreateView方法,和Factory2不同的時,這裡的引數沒有父View
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
            //如果沒有Factory或者Factory2,就會尋找mPrivateFactory(本質上也是Factory2)
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

這段程式碼出自LayoutInflater中createViewFromTag()方法,作用是根據View的名稱(name引數)來建立View,這裡在原始碼篇已經詳細分析過,如果沒有看過,可以點選這裡。

在這裡就簡單描述下,這個方法的主要流程:

  1. 對一些特殊標籤,做分別處理,例如:view,TAG_1995(blink)

  2. 進行對Factory、Factory2的設定判斷,如果設定那麼就會通過設定Factory、Factory2進行生成View

  3. 如果沒有設定Factory或Factory2,那麼就會使用LayoutInflater預設的生成方式,進行View的生成

在實戰篇中,只有第二部分和我們今天的內容是相關的,我們在看一遍第二條。

進行對Factory、Factory2的設定判斷,如果設定那麼就會通過設定Factory、Factory2進行生成View

如果設定了Factory或者Factory2,那麼就不會使用LayoutInflater預設的生成方式,那麼生成View的過程,就由我們自主把控,這才是我們自定義LayoutInflater.Factory的主要原因。

自定義Factory還是Factory2 ?

            View view;
            //如果Factory2存在,就會呼叫其onCreateView方法
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
                //如果Factory存在,就會呼叫其onCreateView方法,和Factory2不同的時,這裡的引數沒有父View
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

我們能夠從這段程式碼中得出,Factory2比Factory的優先順序要高,即Factory2存在Factory就不可能會被呼叫,同理可以得出結論:

優先順序順序:

mFactory2  > mFactory > mPrivateFactory > LayoutInflater預設處理方式

而且我們還能夠發現mFactory2的onCreateView()方法與mFactory是不相同的:

//mFactory2
mFactory2.onCreateView(parent, name, context, attrs);

//mFactory
view = mFactory.onCreateView(name, context, attrs);

根據上述的分析,我們可以得出結論:

(1)Factory2的呼叫優先順序比Factory要高

(2)Factory2的onCreateView()方法,會比Factory多返回一個父View的引數。

(3)Factory2和Factory是互斥的,(如果不通過反射的話)只能設定一個。

第三條在CreateViewFromTag的那篇文章已經分析過了,這裡不做過多的解釋了。

實際選擇的過程中,一般會選擇自定義Factory2,因為Factory2本身也繼承了Factory介面,而且Factory2的優先順序比較高。

注意事項

(1)設定Factory但是發現無響應,是因為本身LayoutInflater中存在Factory2

因為一般使用方式,是直接呼叫cloneInContext()方法,我們知道一般的預設解析器都是PhoneLayoutInflater,我們看下其實現方式:

    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
    }

本質就是呼叫LayoutInflater的兩參構造方法:

    protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    }

在這裡可以看出,cloneInContext會把原LayoutInflater的Factory2和Factory一併複製。

因為Factory比Factory2的優先順序低,所以才會不出現效果。

解決方案 :

(1)自定義LayoutInflater,並且改寫cloneInContext,使其不復制原LayoutInflater的Factory2以及Factory。

public class CustomLayoutInflater extends LayoutInflater {

    protected CustomLayoutInflater(Context context) {
        super(context);
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        return new CustomLayoutInflater(newContext);
    }
}

(2)使用時,直接通過new出例項,然後setFactory

       CustomLayoutInflater newInflater = new CustomLayoutInflater(getActivity());
        newInflater.setFactory2(new CustomAppFactory(newInflater, this));
        return newInflater.inflate(layoutId, null);

(2)使用AppCompatActivity直接setFactory2或者setFactory為什麼報錯?

這是因為 AppCompatActivity 在初始化的時候,已經設定了 Factory,下面來看下這部分程式碼

  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        //注意這個方法
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        //.....省略多餘的程式碼..........
        }
        super.onCreate(savedInstanceState);
    }

繼續檢視 installViewFactory()方法

   @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
        //這句話是設定 Factory 的方法
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            //省略部分程式碼。。。。。。      
        }
    }

可以發現,在onCreate 時 LayoutInflater 已經設定過一次 Factory 了,然後我再來看下 setFactory() 的原始碼:

    public void setFactory(Factory factory) {
        if (mFactorySet) {
        //原因就是這一句
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = factory;
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }

根據上面程式碼,就可以發現報錯原因了。

解決方案 :

在使用前,先使用 cloneInContext()克隆出一個新的 LayoutInflater,然後在進行設定操作。

LayoutInflate  newInflater = LayoutInflater.cloneInContext(inflater,context);

newInflater.setFactory(new CustomFactory());

這樣就避開在原 LayoutInflater 設定 Factory 報錯了。

自定義Factory2的實現 ——> CustomAppFactory

根據上面的展示效果,我們可以判斷出是ViewPager + Fragment的風格,所以我們自定義Factory應該在Fragment的onCreateView中,更改LayoutInflater。

而且根據注意事項,我們一般會自定義優先順序較高的Factory2,防止本身cloneInContext的LayoutInflater中已經存在Factory2,我們使用Factory會無效。

使用方式:

    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Bundle bundle = getArguments();
        int layoutId = bundle.getInt(LAYOUT_ID);
        //注意需要呼叫cloneInContext方法生成新的LayoutInflater
        LayoutInflater newInflater = inflater.cloneInContext(getActivity());
        //呼叫的是setFactory2而非setFactory
        newInflater.setFactory2(new CustomAppFactory(newInflater, this));
        return newInflater.inflate(layoutId, null);
    }

自定義過程

那麼就建立一個類CustomAppFactory來實現Factory2的介面,複寫onCreateView的方法。

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View view = null;
        //<<<<<<<<<<<<<<<<<<<<<<<<<<<第一部分>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        try {
            if (name.contains(".")) {
                String checkName = name.substring(name.lastIndexOf("."));
                String prefix = name.substring(0, name.lastIndexOf("."));
                view = defaultInflater(checkName, prefix, attrs);
            }
            if (name.equals("View") || name.equals("ViewGroup")) {
                view = defaultInflater(name, sClassPrefix[1], attrs);
            } else {
                view = defaultInflater(name, sClassPrefix[0], attrs);
            }
            //<<<<<<<<<<<<<<<<<<<<<<<<<<<第二部分>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
            //例項化完成
            if (view != null) {
                //獲取自定義屬性,通過標籤關聯到檢視上
                setViewTag(view, context, attrs);
                mInflaterView.addView(view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return view;
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View view = onCreateView(name, context, attrs);
        return view;
    }

其實如果我們採取自定義的方式,這裡只會呼叫onCreateView()四位引數的方法,因為在比較Factory2和Factory的程式碼也介紹過了。

我們實現的邏輯是在onCreateView()三位邏輯裡面,因為需要實現的效果不需要Parent(父View),所以這裡邏輯實現全在三位引數的onCreateView()中。

在這裡我們將onCreateView()中,分成2部分內容:

(1)根據名稱解析出View

(2)擴充套件操作,將額外的屬性,提取出來儲存在Tag中

onCreateView第一部分內容

         if (name.contains(".")) {
                String checkName = name.substring(name.lastIndexOf("."));
                String prefix = name.substring(0, name.lastIndexOf("."));
                view = defaultInflater(checkName, prefix, attrs);
            }
            if (name.equals("View") || name.equals("ViewGroup")) {
                view = defaultInflater(name, sClassPrefix[1], attrs);
            } else {
                view = defaultInflater(name, sClassPrefix[0], attrs);
            }

這裡判斷了name中是否包含“.”,是用來判斷生成的View是否是自定義View,下面來看下自定義View和Android自帶的元件的區別:

//原生的元件
RelativeLayout
//自定義View
com.demo.guidepagedemo.customview.CustomImageView

可以發現區別為原生的View不帶字首,而自定義View是包括字首的,所以會用name.contains(“.”)來區分。

而原生元件中View和ViewGroup是屬於android.view包下,其他的例如:RelativeLayout,LinearLayout是屬於android.widget包下。

    private final String[] sClassPrefix = {
            "android.widget.",
            "android.view."
    };

所以在之後會對View和ViewGroup作區分,上面把sClassPrefix貼出來了。

而這裡真正的解析過程最後還是交給LayoutInflater,呼叫LayoutInflater的onCreateView方法:

    private View defaultInflater(String name, String prefix, AttributeSet attrs) {
        View view = null;
        try {
            view = mInflater.createView(name, prefix, attrs);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return view;
    }

LayoutInflater的onCreateView方法這裡就不介紹了,在這裡已經分析過了

onCreateView第二部分內容

            //例項化完成
            if (view != null) {
                //獲取自定義屬性,通過標籤關聯到檢視上
                setViewTag(view, context, attrs);
                mInflaterView.addView(view);
            }

在這裡做拓展處理的,setViewTag方法是處理View的自定義屬性,然後將這些屬性包裝成類,給View設定Tag

setViewTag方法

    /**
     * 將View的屬性資訊儲存在Tag中
     */
    private void setViewTag(View view, Context context, AttributeSet attrs) {
        //解析自定義的屬性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomImageView);
        if (attrs != null && array.length() > 0) {
            AttrTagBean bean = new AttrTagBean();
            bean.xIn = array.getFloat(R.styleable.CustomImageView_in_value_x, 0f);
            bean.xOut = array.getFloat(R.styleable.CustomImageView_out_value_x, 0f);
            bean.yIn = array.getFloat(R.styleable.CustomImageView_in_value_y, 0f);
            bean.yOut = array.getFloat(R.styleable.CustomImageView_out_value_y, 0f);
            //index
            view.setTag(bean);
        }
        array.recycle();
    }

上面對應的是本文我們開始設定的4個係數:

R.styleable.CustomImageView_in_value_x              -->   進入時 x方向的係數

R.styleable.CustomImageView_out_value_x             -->   退出時 x方向的係數

R.styleable.CustomImageView_in_value_y              -->   進入時 y方向的係數

R.styleable.CustomImageView_out_value_y             -->   退出時 y方向的係數

而這裡的mInflaterView是一個抽象介面,讓Fragment來實現的,通過在Fragment中內建一個List《View》,到時候可以遍歷統一操作這些View,下面是實現過程:

public interface InflaterViewImpl {

    /**
     * 獲取View集合
     *
     * @return
     */
    List<View> getViews();


    /**
     * 新增元素
     */
    void addView(View view);
}

Fragment中的實現過程:

public class PageFragment extends Fragment implements InflaterViewImpl {

    private List<View> views = new ArrayList<>();

    //**************篇幅原因省略了部分方法************************//

    @Override
    public List<View> getViews() {
        return views;
    }

    @Override
    public void addView(View view) {
        if (views.contains(view)) {
            return;
        }
        views.add(view);
    }
}

處理ViewPager的滑動

這是實戰篇的最後一部分內容,主要介紹的是ViewPager的滑動監聽相關的處理,因為所有效果是基於ViewPager的滑動監聽來顯示的。

因為本文主要介紹內容是自定義LayoutInflater.Factory,所以這裡會簡單敘述下:

 mInflaterVp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                //獲取ViewPager的寬度
                int vpWidth = mInflaterVp.getWidth();
                //獲取正在進入的介面
                PageFragment inFragment = getPosition(position - 1);
                if (inFragment != null) {
                    List<View> views = inFragment.getViews();
                    if (views != null && views.size() > 0) {
                        for (View view : views) {
                            AttrTagBean tag = (AttrTagBean) view.getTag();
                            if (tag != null) {
                                view.setTranslationX((vpWidth - positionOffsetPixels) * tag.xIn);
                                view.setTranslationY((vpWidth - positionOffsetPixels) * tag.yIn);
                            }
                        }
                    }
                }

                //當前正在滑動的介面
                PageFragment outFragment = getPosition(position);
                if (outFragment != null) {
                    List<View> views = outFragment.getViews();
                    if (views != null && views.size() > 0) {
                        for (View view : views) {
                            AttrTagBean tag = (AttrTagBean) view.getTag();
                            if (tag != null) {
                                view.setTranslationX((0 - positionOffsetPixels) * tag.xOut);
                                view.setTranslationY((0 - positionOffsetPixels) * tag.yOut);
                            }
                        }
                    }
                }
            }

            @Override
            public void onPageSelected(int position) {
                //當劃到最後一頁時,小人的圖示消失
                if (position == fragments.size() - 1) {
                    mInflaterIv.setVisibility(View.GONE);
                } else {
                    mInflaterIv.setVisibility(View.VISIBLE);
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                //這裡是處理圖中的小人的幀動畫過程
                Drawable anim = mInflaterIv.getBackground();
                if (!(anim instanceof AnimationDrawable)) {
                    return;
                }
                AnimationDrawable animation = (AnimationDrawable) anim;
                Log.d("滑動狀態", state + "");
                switch (state) {
                    //空閒狀態
                    case ViewPager.SCROLL_STATE_IDLE:
                        animation.stop();
                        break;
                    //拖動狀態
                    case ViewPager.SCROLL_STATE_DRAGGING:
                        animation.start();
                        break;
                    //慣性滑動狀態
                    case ViewPager.SCROLL_STATE_SETTLING:
                        break;
                }
            }
        });

Demo

本文的所有程式碼已上傳到CSDN的資源中心

Demo中包含兩種方式實現本文的效果:

(1)自定義View方式

(2)自定義LayoutInflater.Factory

拓展

其實當天下載的小紅書的App 後,發現引導頁,並不是實戰篇的樣子。

小紅書引導頁

小紅書引導頁

不得不說,這種實現方式,博主感覺挺有靈性的,簡潔而不失觀賞性。

然後博主就高仿了一波,下面是 Demo 地址。

因為這個需求和最近寫的主題無關,就不打算寫部落格了,所以直接放出連結了。

先宣告,這個是有償的,也不多就2元,如果有實際需求的同學,可以下載下支援一下博主。

相關推薦

Android LayoutInflater佈局載入實戰

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 效果 可以看出在滑動時,會出現視覺差效果。 可以看出在滑動時,物品會飄出去。

Android LayoutInflater佈局載入原始碼

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 (1)Activity 的 getSystemService的實現過程 (2

Android LayoutInflater佈局載入介紹

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 本篇作為Android 中LayoutInflater(佈局載入器)系列的介紹篇,該篇內容知識內容比較基礎,建議先看一些概述,如果感覺

Android LayoutInflater佈局載入原始碼parseInclude方法

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是作為Android中LayoutInflater(佈局載入器)原

Android LayoutInflater佈局載入原始碼rInflate方法

前言 如果讀者沒有閱讀過該系列部落格,建議先閱讀下博文說明,這樣會對後續的閱讀部落格思路上會有一個清晰的認識。 導航 概述 本篇部落格,是屬於Android 中LayoutInflater(佈局載入器)原始

PyQt52——調整佈局佈局管理第一個程式

我們拖拽一個UI檔案,轉為PY檔案後生成一個類Ui_MainWindow 此時,我們新建一個檔案,用來控制業務邏輯(繼承介面中的類),跟介面分開,這樣我們就完成了介面和邏輯相分離(這段程式碼使用率基本100%,牢牢記住)。 1 __author__ = "WSX" 2 import sys 3

Scrapy爬蟲入門教程七 Item Loaders專案載入

目錄 專案載入器 巢狀裝載器 開發環境: Python 3.6.0 版本 (當前最新) Scrapy 1.3.2 版本 (當前最新) 專案載入器 專案載入器提

連結資料庫報錯Communications link failure解決

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet

JavaGUI簡介、AWT概述、以及佈局管理流式佈局管理、邊界佈局管理、網格佈局管理、網格包佈局管理、卡片佈局管理

1 GUI簡介   GUI的全稱是Graphical User Interface,即圖形使用者介面。顧名思義,就是應用程式提供給使用者操作的圖形介面,包括視窗、選單、按鈕、工具欄和其他各種使用者介面元素。Java中針對GUI設計提供了豐富的類庫,這些類分別位

AndroidListView上拉載入分頁功能

思路 1新增頁尾,並隱藏 2監聽滑動事件,判斷當滑到低部時,顯示頁尾,並載入資料(介面回撥到activity中載入) 3資料新增完成之後隱藏頁尾 效果圖: 專案結構: 自定義listView類LoadListView package com.zhh.android;

AndroidListView錯位佈局實現無聊向

由於某些原因,需要個錯位的頁面,在網上找不到好的例子,試著動手寫了寫。 不考慮配色的完成圖如下: 首先考慮的是,listview每一行左右都有可能縮排。 先假設一行的佈局就是ImageView,TextView,ImageView,程式碼如下: 1 <Line

WinForm,每隔一段時間(參數)調用一次函數使用定時

pre tick break switch 時間 器) chan pri args 1      System.Windows.Forms.Timer setTimer; //定義一個定時器 2 int flg = 0;

_030_Android_ Android開發SmsManager簡訊管理詳解

轉自https://blog.csdn.net/qq_37443229/article/details/80039836,感謝作者的無私分享。 Android開發之SmsManager(簡訊管理器)詳解         SmsManager是

反射Constructor、Field、Method、類載入

一:什麼是反射 在認識反射之前,首先回顧下什麼是正向處理。如果我們需要例項化一個物件,首先需要匯入這個類的包,在這個包中找這個類: package CODE.反射; import java.util.Date; public class Fan { public static

1600802047 android 第三次作業音樂播放

一、實現的功能 播放、暫停、上一首、下一首    顯示列表 二、UI介面截圖   第一首歌   第二首歌   第三首歌   第四首歌 list列表   點選播放音樂時圖片旋轉,點選上一首切換上一

一起Talk Android第一百回:Android使用自定義控制元件

各位看官們,大家好,上一回中咱們說的是Android中使用自定義佈局的例子,這一回說的例子是Android中使用自定義控制元件。閒話休提,言歸正轉。讓我們一起Talk Android吧! 看官們,我們在上一回中通過自定義佈局巧妙地實現了分隔線,不過這個分隔線中看

Android開發筆記一百六十休眠模式下的定時控制

定時器AlarmManager常常用於需要週期性處理的場合,比如鬧鐘提醒、任務輪詢等等。並且定時器來源於系統服務,即使App已經不在運行了,也能收到定時器發出的廣播而被喚醒。似此迴光返照的神技,便遭到開發者的濫用,造成使用者手機充斥著各種殺不光程序,就算通過手機安全工具一再地

Android自定義DataTimePicker日期選擇

package com.wwj.datetimepicker; import java.text.SimpleDateFormat; import java.util.Calendar; import android.app.Activity; import android.app.AlertDialog

AndroidListview--排序ListView

 ListView的A-Z字母排序和過濾搜尋功能並且實現漢字轉成拼音的功能,我們知道一般我們對聯絡人,城市列表等實現A-Z的排序,因為聯絡人和城市列表我們可以直接從資料庫中獲取他的漢字拼音,而對於一般的資料,我們怎麼實現A-Z的排序,我們需要將漢字轉換成拼音就行

Android屬性動畫 TimeInterpolator插值

OK,繼續學習屬性動畫,本篇文章是屬性動畫系列的第三篇文章了,今天來學習一下屬性動畫中的TimeInterpolator,如果你對屬性動畫還不太熟悉,可以點選下面的連結學習一下前兩篇文章的知識: 1.介紹 先說說Interpolator,在And