Android Support Design 庫 之 Snackbar使用及源碼分析

 

在谷歌提出 material design 之后,終于推出了 android.support.design 這個官方的material design庫,這幾天我也簡單瀏覽了下這個庫,基本上我們常用的組件都有了,從今天開始,就可以一步步替換掉

以前使用的github上的那些開源控件了,畢竟谷歌出品 才屬精品~~另外分析這個design庫的源碼我認為是非常有意義的,android上的app 在以前各家都有各家的風格,但是在谷歌出了material design這門新的

設計語言以及官方的庫以后,相信越來越多的app 會逐步優化自己的ui 來符合官方的標準,學習這個design庫的源碼可以讓我們以后改寫自定義控件的時候更加柔韌有余。

首先,來看一下這個官方的介紹。http://www.google.com/design/spec/components/snackbars-toasts.html#

這個文章系統的闡述了 snackbar和toast的區別和正確使用snackbar的方式。

我簡單歸納如下:

1.比toast更加好,畢竟snackbar 可以響應點擊事件

2.snackbar 同一時間有且只有一個在顯示。

3.snackbar 上不要有圖標

4.snackbar上action 只能有一個。

5.如果有懸浮按鈕 floating action button的話,snackbar 在彈出的時候 不要覆蓋這個button.

6.此外我個人認為snackbar 在一定程度上可以替代dialog的某些應用場景。比如以前網絡不通的情況下 我們登陸失敗,會給一個dialog提示,現在就可以用snackbar 來做這個有action的提示 更加方便快捷。

使用snackbar:

1.導入support design 庫 (這一步在以后的design庫的 控件文章里都會舍去)

首先找到你app的build gradle文件

Android Support Design 庫 之 Snackbar使用及源碼分析

然后增加一個compile語句即可

compile 'com.android.support:design:22.2.0'

Android Support Design 庫 之 Snackbar使用及源碼分析

2.編寫xml文件以及java文件

<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=".MainActivity"
    android:id="@+id/layout">


    <!-- 因為snackbar 需要有一個父控件所以 我們暫時就用tv 來做他的父控件-->
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:gravity="center"
        android:text="Bottom layout" />

</RelativeLayout>

package com.example.burning.myapplication;

import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity {

    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.tv);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //這個地方第一個參數    傳進去的是tv    但是實際上你無論傳進去什么值 snackbar都一定是從屏幕的最底端出現的    原因在源碼
                //分析那邊可以看到
                Snackbar.make(tv, "connection error", Snackbar.LENGTH_LONG).setAction("retry", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        tv.setText("aleady click snackbar");
                    }
                }).show();

            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

最后我們來看下效果

Android Support Design 庫 之 Snackbar使用及源碼分析

 

 

 然后我們來看一下 如果和正常的FAB(懸浮按鈕)在一起會有什么效果(注意這里的懸浮按鈕我們也使用design庫里的并不使用github上開源的)

先看一下xml文件

<RelativeLayout 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: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=".MainActivity">


    <FrameLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/btnFloatingAction"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:src="@drawable/ic_plus"
            app:borderWidth="0dp"
            app:fabSize="normal" />
    </FrameLayout>
</RelativeLayout>

activity代碼
package com.example.burning.myapplication;

import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;


public class MainActivity extends ActionBarActivity {

    private ViewGroup layout;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layout = (ViewGroup) this.findViewById(R.id.layout);
        layout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(layout, "connection error", Snackbar.LENGTH_LONG).setAction("retry", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                    }
                }).show();

            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

來看一下運行效果

Android Support Design 庫 之 Snackbar使用及源碼分析

大家可以看到當我們的snackbar在彈出的時候    會覆蓋到我們的FAB,那這體驗是非常糟糕的,這里也給出一個完美的解決方案

其實也很簡單用一下design庫里的layout即可    java代碼不需要改變    只要稍微改一下布局文件即可

<RelativeLayout 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: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=".MainActivity">


    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/btnFloatingAction"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:src="@drawable/ic_plus"
            app:borderWidth="0dp"
            app:fabSize="normal" />
    </android.support.design.widget.CoordinatorLayout>
</RelativeLayout>

就是換了一個新的layout而已。

來看下運行的效果。

 

 Android Support Design 庫 之 Snackbar使用及源碼分析

 

當然了 你要改變這個snackbar的背景色也是可以的 只需要

Snackbar sb=Snackbar.make(layout, "connection error", Snackbar.LENGTH_LONG).setAction("retry", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                    }
                });
                //紅色
                sb.getView().setBackgroundColor(0xfff44336);
                sb.show();

基本的用法 要點就是這些,我們來看一下這個snackbar的源碼   看看谷歌官方是如何編寫 material design 風格控件的

這里我無法貼上全部源碼 因為太多,只挑重要的流程說 可以從make開始。

//這個地方就是構造函數
    Snackbar(ViewGroup parent) {
        this.mParent = parent;
        this.mContext = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(this.mContext);
        this.mView = (Snackbar.SnackbarLayout)inflater.inflate(layout.layout_snackbar, this.mParent, false);
    }

    //這個地方就是我們調用的make函數 也就是整個類的一個入口 在這里可以看到 我們的代碼轉入了snackbar的構造函數
    public static Snackbar make(View view, CharSequence text, int duration) {
        //注意看這個地方調用了 findsuitableparent這個函數
        Snackbar snackbar = new Snackbar(findSuitableParent(view));
        snackbar.setText(text);
        snackbar.setDuration(duration);
        return snackbar;
    }

    public static Snackbar make(View view, int resId, int duration) {
        return make(view, view.getResources().getText(resId), duration);
    }

    //這個函數其實作用就是無論你傳進去的是什么view 我最終都會遍歷到framlayout為止,因為activity的最外層實際上就是一個framlayout
    //所以你在調用make函數的時候無論傳什么值進去 snackabr都會從最底部彈出來 就是因為這個函數做了這樣的工作 但是!!!!
                //如果在遍歷到最頂部的framlayout之前 遇到了一個framelayout 那么就會從這個framlayout的底部彈出,而不會從屏幕的最下方彈出了。
    //這個地方CoordinatorLayout的優先級比framlayout還要高 所以你如果穿進去的view是CoordinatorLayout的話 這個snackbar 就一定會從
    //CoordinatorLayout 底部彈出了。如果你CoordinatorLayout的最底部恰好在屏幕中間 那么snackbar 就會從屏幕中間彈出  而不會從底部彈出 這一點一定要注意
    @Nullable
    private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;

        do {
            if(view instanceof CoordinatorLayout) {
                return (ViewGroup)view;
            }

            if(view instanceof FrameLayout) {
                if(view.getId() == 16908290) {
                    return (ViewGroup)view;
                }

                fallback = (ViewGroup)view;
            }

            if(view != null) {
                ViewParent parent = view.getParent();
                view = parent instanceof View?(View)parent:null;
            }
        } while(view != null);

        return fallback;
    }

大家可以看一下第六行。實際上這個mView就是一個內部類的對象

1 private final Snackbar.SnackbarLayout mView;

Android Support Design 庫 之 Snackbar使用及源碼分析

 

然后接著看第六行的xml文件(到這里其實我們也能猜到了 真正自定義view的snackbar是由snackbar的內部類snackbarlayout來完成的)

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2015 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
-->

<!--注意看class那邊的寫法  那個$就是代表內部類的一個符號 這個技巧 以后我們自己自定義控件的時候也可以學習-->
<view xmlns:android="http://schemas.android.com/apk/res/android"
      class="android.support.design.widget.Snackbar$SnackbarLayout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom"
      style="@style/Widget.Design.Snackbar" /><!-- From: file:/usr/local/google/buildbot/repo_clients/https___googleplex-android.googlesource.com_a_platform_manifest.git/lmp-mr1-supportlib-release/frameworks/support/design/res/layout/layout_snackbar.xml -->

繼續看下內部類

Android Support Design 庫 之 Snackbar使用及源碼分析

找到我們真正的snackbar的布局文件 注意這個地方討巧的使用了merge標簽 這是一個比較好的優化xml的 寫法

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2015 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
-->

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

    <TextView
            android:id="@+id/snackbar_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:paddingTop="@dimen/snackbar_padding_vertical"
            android:paddingBottom="@dimen/snackbar_padding_vertical"
            android:paddingLeft="@dimen/snackbar_padding_horizontal"
            android:paddingRight="@dimen/snackbar_padding_horizontal"
            android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
            android:maxLines="@integer/snackbar_text_max_lines"
            android:layout_gravity="center_vertical|left|start"
            android:ellipsize="end"/>

    <TextView
            android:id="@+id/snackbar_action"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/snackbar_extra_spacing_horizontal"
            android:layout_marginStart="@dimen/snackbar_extra_spacing_horizontal"
            android:layout_gravity="center_vertical|right|end"
            android:background="?attr/selectableItemBackground"
            android:paddingTop="@dimen/snackbar_padding_vertical"
            android:paddingBottom="@dimen/snackbar_padding_vertical"
            android:paddingLeft="@dimen/snackbar_padding_horizontal"
            android:paddingRight="@dimen/snackbar_padding_horizontal"
            android:visibility="gone"
            android:textAppearance="@style/TextAppearance.Design.Snackbar.Action"/>

</merge><!-- From: file:/usr/local/google/buildbot/repo_clients/https___googleplex-android.googlesource.com_a_platform_manifest.git/lmp-mr1-supportlib-release/frameworks/support/design/res/layout/layout_snackbar_include.xml -->

到這里 主要的snackbar的一個加載流程就分析完畢了,很多諸如動畫的代碼部分 我就暫時不去分析他了,大家可以自己仔細分析。

另外有心的同學可能發現了這么一個代碼

Android Support Design 庫 之 Snackbar使用及源碼分析

 

實際上Behavior 我個人認為是這次support design庫里面最重要的一個東西,以后我會單獨出來講一下。基本上support design包里 每一個控件都有她的身影出沒。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!