Android打造不一樣的EmptyView

來源:互聯網
上載者:User

標籤:android   emptyview   

大家都對ListView非常熟悉,目測也會經常使用ListView的一個方法setEmptyView,來設定當資料載入中或者資料載入失敗的一個提醒的效果,這個方法雖然使用起來簡單,但是如果你提供一個複雜的布局,例如:

在資料載入失敗後,添加一個Button讓使用者可以選擇重新載入資料。

那麼,你可能會這麼做,find這個button,然後給button設定點擊事件,好吧。。。一個兩個的還可以忍受,那多了呢?比如我遇到的這個情況,在測試階段,老闆讓加一個重新整理的功能,要是按照這種方法,估計現在現在我還在加班(2015/7/27 23:00),那有沒有一種更加方便的方式,幾行代碼就可以搞定?而且不需要寫那些煩人的setOnClickListener?能不能提供一個不僅僅局限於ListViewEmptyView,因為我不僅僅在ListView上使用。

答案是肯定的,這篇部落格,我們就去實現一個這樣的組件,在實現之間,我們來看看ListView和他的EmptyView是怎麼一個關係,首先定位到ListView.setEmptyView方法:

   @android.view.RemotableViewMethod    public void setEmptyView(View emptyView) {        mEmptyView = emptyView;        // If not explicitly specified this view is important for accessibility.        if (emptyView != null                && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {            emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);        }        final T adapter = getAdapter();        final boolean empty = ((adapter == null) || adapter.isEmpty());        updateEmptyStatus(empty);    }

繼續跟進代碼updateEmptyStatus

private void updateEmptyStatus(boolean empty) {    if (isInFilterMode()) {        empty = false;    }    if (empty) {        if (mEmptyView != null) {            mEmptyView.setVisibility(View.VISIBLE);            setVisibility(View.GONE);        } else {            // If the caller just removed our empty view, make sure the list view is visible            setVisibility(View.VISIBLE);        }        // We are now GONE, so pending layouts will not be dispatched.        // Force one here to make sure that the state of the list matches        // the state of the adapter.        if (mDataChanged) {            this.onLayout(false, mLeft, mTop, mRight, mBottom);        }    } else {        if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);        setVisibility(View.VISIBLE);    }}

唉,原來也沒啥,看代碼31~37行,就是根據資料是否為空白,來控制顯示mEmptyView和ListView本身。
既然原理簡單,那麼我們完全可以自己實現一個。但是,我們的原理正好和ListView的這個相反:

ListView是通過綁定一個emptyView實現的
而我們,是通過EmptyView綁定ListView(其他view也ok)實現的。
我們的EmptyView提供一個通用的方式,載入中時提醒載入中,載入失敗提醒載入失敗,並提供一個Button供使用者重新整理使用。

分析完了,接下來就是編碼了,首先我們繼承一個RelativeLayout來實現這麼一個布局:

public class EmptyView extends RelativeLayout {    private String mText;    private String mLoadingText;    private TextView mTextView;    private Button mButton;  private View mBindView;...}

簡單說一下4個變數的作用。
mText表示資料為空白時提醒的文本。
mLoadingText表示載入中提醒的文本。
mTextView顯示提醒文本。
mButton提供給使用者重新整理的按鈕。
mBindView我們要綁定的view。

ok,繼續代碼:

public class EmptyView extends RelativeLayout {    ...    public EmptyView(Context context, AttributeSet attrs) {        super(context, attrs);        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EmptyView, 0, 0);        String text = ta.getString(R.styleable.EmptyView_android_text);        String buttonText = ta.getString(R.styleable.EmptyView_buttonText);        mLoadingText = ta.getString(R.styleable.EmptyView_loadingText);        ta.recycle();        init(text, buttonText);    }...}

為了靈活性,這些常值內容我們定義成可以在xml中配置使用,哎?怎麼還有一個buttonText,這個當然是按鈕上的文字了。
繼續代碼,可以看到調用了init方法。
來看看:

public class EmptyView extends RelativeLayout {    ...    private void init(String text, String buttonText) {        if(TextUtils.isEmpty(text)) text = "暫無資料";        if(TextUtils.isEmpty(buttonText)) buttonText = "重試";        if(TextUtils.isEmpty(mLoadingText)) mLoadingText = "載入中...";        mText = text;        mTextView = new TextView(getContext());        mTextView.setText(text);        LayoutParams textViewParams = new LayoutParams(                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);        textViewParams.addRule(RelativeLayout.CENTER_IN_PARENT);        mTextView.setId(R.id.id_empty_text);        addView(mTextView, textViewParams);        mButton = new Button(getContext());        mButton.setText(buttonText);        LayoutParams buttonParams = new LayoutParams(                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);        buttonParams.addRule(RelativeLayout.CENTER_HORIZONTAL);        buttonParams.addRule(RelativeLayout.BELOW, R.id.id_empty_text);        addView(mButton, buttonParams);    }    ...}

init方法中,上來,我們去判斷這些文本是否為空白,如果為空白,提供預設的文本。接下來new了一個TextViewButton並添加到該控制項中,TextViewButton是上下排列的。至此,布局已經完成了,那怎麼控制呢?我們想要的是什麼效果呢?

在資料載入的時候調用loading方法,顯示正在載入中的文本。
在資料載入成,隱藏該view。
在資料載入失敗,顯示載入失敗的文本,並提供一個按鈕去重新整理資料。

ok,我們按照這個條目一個個的來實現,首先是loading

public class EmptyView extends RelativeLayout {    ...    public void loading() {    if(mBindView != null) mBindView.setVisibility(View.GONE);        setVisibility(View.VISIBLE);        mButton.setVisibility(View.INVISIBLE);        mTextView.setText(mLoadingText);    }    ...}

loading方法很簡單,首先判斷mBindView是否為空白,不為空白則隱藏它,然後讓該控制項可見,繼續讓Button不可見,因為在載入中的時候,我們不允許點擊的發生。最後就是讓TextView顯示正在載入中的文本。
繼續看看載入成功的方法,這個更簡單啦。

public class EmptyView extends RelativeLayout {    ...    public void success() {        setVisibility(View.GONE);    if(mBindView != null) mBindView.setVisibility(View.VISIBLE);    }    ...}

只有兩行代碼,就是讓該控制項隱藏,讓綁定的view顯示。
那麼載入失敗呢?同樣簡單!

public class EmptyView extends RelativeLayout {    ...    public void empty() {    if(mBindView != null) mBindView.setVisibility(View.GONE);        setVisibility(View.VISIBLE);        mButton.setVisibility(View.VISIBLE);        mTextView.setText(mText);    }    ...}

不多說了,唯一注意的就是我們讓Button顯示了。
至此,我們整個效果就完成了,在載入資料的時候調用loading方法來顯示載入中的文本,載入失敗後,調用empty來顯示載入失敗的文本和重新整理的按鈕,在載入成功後直接隱藏控制項!
控制項倒是完成了,我們還不知道mBindView怎麼來的,其實也很簡單。我們在代碼中需要調用bindView(View view)方法來指定。

public class EmptyView extends RelativeLayout {    ...    public void bindView(View view) {        mBindView = view;    }    ...}

哈哈,剩下最後一個問題了,按鈕的點擊事件怎麼做?難道要在使用的時候添加onClick事件?哎,那樣太麻煩了,要知道,我有很多檔案要改的,我希望一行代碼就可以搞定!

亮點來了:

public class EmptyView extends RelativeLayout {    ...    public void buttonClick(final Object base, final String method,            final Object... parameters) {        mButton.setOnClickListener(new OnClickListener() {            public void onClick(View v) {                int length = parameters.length;                Class<?>[] paramsTypes = new Class<?>[length];                for (int i = 0; i < length; i++) {                    paramsTypes[i] = parameters[i].getClass();                }                try {                    Method m = base.getClass().getDeclaredMethod(method, paramsTypes);                    m.setAccessible(true);                    m.invoke(base, parameters);                } catch (Exception e) {                    e.printStackTrace();                }            }        });    }    ...}

利用反射去做,我們只需要指定調用哪個對象上的哪個方法,需要參數的話就傳入參數即可。
這段代碼簡單說一下,首先我們給button設定了一個點擊事件,在事件響應的時候,首先遍曆參數,擷取參數的類型。然後根據方法名反射出方法,最後直接invoke去執行。這樣我們使用起來就非常方便了,完成了我們一行代碼搞定的目標。
好激動,來測試一下吧:
先看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"    tools:context=".MainActivity">    <loader.org.emptyview.EmptyView        android:id="@+id/empty_view"        android:layout_width="match_parent"        android:layout_height="match_parent"/>    <TextView        android:id="@+id/name"        android:layout_width="match_parent"        android:layout_height="wrap_content"/></RelativeLayout>

我們沒有使用ListView,而是使用了一個TextView,再來看看在Activity中怎麼調用:

public class MainActivity extends AppCompatActivity {    private EmptyView mEmptyView;    private TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mEmptyView = (EmptyView) findViewById(R.id.empty_view);        mTextView = (TextView) findViewById(R.id.name);        mEmptyView.bindView(mTextView); // 設定bindView        mEmptyView.buttonClick(this, "loadData"); // 當button點擊時調用哪個方法        loadData();    }    /**     * 載入資料     */    private void loadData() {        mEmptyView.loading(); // 載入中        // 2s後出結果        new Handler().postDelayed(new Runnable() {            @Override            public void run() {                Random r = new Random();                int res = r.nextInt(2);                // 失敗                if(res == 0) {                    mEmptyView.empty(); // 顯示失敗                }else {                    // 成功                    mEmptyView.success();                    mTextView.setText("success");                }            }        }, 2000);    }}

首先,我們通過mEmptyView.bindView(mTextView)來設定要綁定的view,這裡當然是TextView了。
接下來,通過mEmptyView.buttonClick(this, "loadData")設定按鈕點擊後執行哪個方法,這裡是當前對象上的loadData方法,並且沒有參數。
getData中類比延遲2s後擷取資料,資料的成功失敗是隨機的,當失敗了,調用empty方法,成功後調用success方法。
哈哈,就是這麼簡單,來看看代碼的效果:


ok~ok~,非常完美。

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Android打造不一樣的EmptyView

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.