編輯:關於Android編程
上一篇講解了TabLayout,接下來我們繼續學習Google I/O 2015 推出的 Android Design Support Library的其他成員,這一篇主要講解android.support.design.widget.Snackbar。
在Android中我們給用戶提示一些信息以便給用戶良好的體驗,在Android 5.0之前,眾所周知可以借助Dialog和Toast,而5.0之後新推出Android Design Support Library裡讓我們多了一種選擇——public final class Snackbar(其實我們暫且可以把它看成加強版的Toast)。實際上Snackbar也借鑒了許多Toast的方法機制,主要用於在顯示提示信息的同時提供一些輕量級的反饋操作,通常他以從下往上的漸進動畫顯示在我們手機屏幕的底部或者大屏幕設備的左下方,支持滑動消失和自動消失。最重要的是他支持setAction操作和監聽Snackbar的顯示和消失事件(通過setCallback來實現)
//從源碼中,這是Snackbar唯一的構造方法,而且還是私有的 private Snackbar(ViewGroup parent) { mParent = parent; mContext = parent.getContext(); LayoutInflater inflater = LayoutInflater.from(mContext); mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false); }
@NonNull public static Snackbar make(@NonNull View view, @NonNull CharSequence text, @Duration int duration) { Snackbar snackbar = new Snackbar(findSuitableParent(view)); snackbar.setText(text); snackbar.setDuration(duration); return snackbar; } @NonNull public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) { return make(view, view.getResources().getText(resId), duration); }
//在對應的Module下的gradle.build腳本文件下 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:support-v4:23.1.1' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' }
由於Snackbar並不是像TextView、ImageIiew這些可視化的組件,不能通過xml方式去靜態構造,只能通過代碼去構造,又其構造方法為私有的,肯定不能直接調用構造方法,而Snackbar的make()方法來創建一個Snackbar對象,make()方法的第一個參數需要傳入一個view,只要是當前界面布局的任意一個view都可以(根布局對象不行),Snackbar會使用這個view來自動查找最外層的布局,用於展示Snackbar。第二個參數就是Snackbar中顯示的內容,第三個參數是Snackbar顯示的時長。
/** *Snackbar.LENGTH_INDEFINITE:長期顯示直到我們手動關閉 ×Snackbar.LENGTH_SHORT *Snackbar.LENGTH_LONG */ Snackbar.make(view,CharSequence,Snackbar.LENGTH_INDEFINITE)
這裡我快速連續點擊了五次按鈕,Toast就觸發了五次。這樣的體驗其實是不好的,因為也許用戶是手抖了一下多點了幾次,導致Toast就長時間關閉不掉了。又或者我們其實已在進行其他操作了,應該彈出新的Toast提示,而上一個Toast卻還沒顯示結束。
主布局文件
android:layout_width="match_parent" android:layout_height="match_parent"> <framelayout android:id="@+id/id_content" android:layout_height="wrap_content" android:layout_width="wrap_content"></framelayout>
使用的時候值得注意的是make裡的第一個參數是傳遞一個View,但是如果我們把這裡的FrameLayout改為主布局的根RelativeLayout的話則不能在Activity加載時顯示即改為mLayout= (RelativeLayout) findViewById(R.id.id_content);為什麼呢?哈哈先透露下原因在Snackbar的構造方法裡,這篇文章先不去深究。
package com.crazymo.snackbardemo; import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.os.Handler; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import android.widget.RelativeLayout; public class MainActivity extends Activity { private FrameLayout mLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLayout= (FrameLayout) findViewById(R.id.id_content); /**注意make裡的第一個參數是傳遞一個View,但是如果我們把這裡的FrameLayout改為主布局的根RelativeLayout的話則不能在Activity加載時顯示 *mLayout= (RelativeLayout) findViewById(R.id.id_content);為什麼呢?哈哈先透露下 * 原因在Snackbar的構造方法裡 */ new Handler().postDelayed(new Runnable() { @Override public void run() { Snackbar.make(mLayout, "Layout's Snackbar msg", Snackbar.LENGTH_INDEFINITE) .setAction("LayoutAction", new View.OnClickListener() { @Override public void onClick(View view) { Log.e("CrazyMO", "RelativeLayout's Snackbar Action excupted!"); } }) .setActionTextColor(Color.parseColor("#bc6e1c")) .setCallback(new Snackbar.Callback() { @Override public void onDismissed(Snackbar snackbar, int event) { super.onDismissed(snackbar, event); Log.e("CrazyMO", "RelativeLayout's SnackbarDismiss"); } @Override public void onShown(Snackbar snackbar) { super.onShown(snackbar); Log.e("CrazyMO", snackbar.getView().getClass().toString()); Log.e("CrazyMO", "RelativeLayout's Snackbar show!"); } }) .show(); } }, 5000);//延時5s顯示 } public void showSnackbar(View v){ Snackbar.make(v, "Botton's Snackbar msg", Snackbar.LENGTH_LONG)//構造Snackbar對象 .setAction("BtnAction", new View.OnClickListener() { //設置Action,其中BtnAction則為顯示在Snackbar的值 @Override public void onClick(View view) { Log.e("CrazyMO", "Button's Snackbar Action excupted!"); } }) .setActionTextColor(Color.parseColor("#236f28")) .setCallback(new Snackbar.Callback() {//設置監聽,Miss和Show事件 @Override public void onDismissed(Snackbar snackbar, int event) { super.onDismissed(snackbar, event); Log.e("CrazyMO", "Button's Snackbar Dismiss"); } @Override public void onShown(Snackbar snackbar) { super.onShown(snackbar); Log.e("CrazyMO", snackbar.getView().getClass().toString()); Log.e("CrazyMO", "Button's Snackbar show!"); } }) .show(); } }
07-26 16:18:44.060 29377-29376/com.crazymo.snackbardemo E/CrazyMO: class android.support.design.widget.Snackbar$SnackbarLayout 07-26 16:18:45.060 29377-29377/com.crazymo.snackbardemo E/CrazyMO: RelativeLayout's Snackbar show!由於這個依附於FrameLayout的Snackbar設置的是不自動消失,點擊LAYOUTACTION之後觸發了setAction和CallBack
07-26 16:23:17.559 29377-29377/com.crazymo.snackbardemo E/CrazyMO: RelativeLayout's Snackbar Action excupted! 07-26 16:23:17.851 29377-29377/com.crazymo.snackbardemo E/CrazyMO: RelativeLayout's SnackbarDismiss再點擊Button顯示Snackbar,由於設置Snackbar自動消失
07-26 16:23:42.354 29377-29373/com.crazymo.snackbardemo E/CrazyMO: class android.support.design.widget.Snackbar$SnackbarLayout 07-26 16:23:43.367 29377-29377/com.crazymo.snackbardemo E/CrazyMO: Button's Snackbar show! 07-26 16:23:46.382 29377-29377/com.crazymo.snackbardemo E/CrazyMO: Button's Snackbar Dismiss
既然我們現在你有三種方案可以給用戶提示信息——Snackbar、Toast和Dialog。或許有點不知所措,結合自己的經驗和理解,小結下使用場景。
Dialog:當提示信息是十分重要的,並且必須要由用戶決定之後,才能進行下一步操作時,優先使用Dialog,比如說確認刪除操作、或者其他不可逆轉的操作時等等。 Toast:當提示信息只是為了告知用戶反饋信息,用戶不需要對這個事情做出響應的時。比如抽獎超過次數了等。//在使用一些app的時候,發現有時候Toast貌似提示重復了,即每一次點擊就又生成了一個Toast,下面這個方案可以完美解決 public class Utils { private static Toast mToast; public static void showToast(Context context, String content,int lentgh) { if (mToast == null) { mToast = Toast.makeText(context, content, lentgh); } else { mToast.setText(content); } mToast.show(); } } Utils.showToast(context, “things happened”,Toast.LENGTH_SHORT);//調用Snackbar:其實所有情況Snackbar可能會是你最好的選擇,只要能用就用呗。
package android.support.design.widget; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.ColorInt; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.design.R; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR; /** * Snackbars provide lightweight feedback about an operation. They show a brief message at the * bottom of the screen on mobile and lower left on larger devices. Snackbars appear above all other * elements on screen and only one can be displayed at a time. * <p> * They automatically disappear after a timeout or after user interaction elsewhere on the screen, * particularly after interactions that summon a new surface or activity. Snackbars can be swiped * off screen. * </p><p> * Snackbars can contain an action which is set via * {@link #setAction(CharSequence, android.view.View.OnClickListener)}. * </p><p> * To be notified when a snackbar has been shown or dismissed, you can provide a {@link Callback} * via {@link #setCallback(Callback)}.</p> */ public final class Snackbar { /** * Callback class for {@link Snackbar} instances. * * @see Snackbar#setCallback(Callback) */ public static abstract class Callback { /** Indicates that the Snackbar was dismissed via a swipe.*/ public static final int DISMISS_EVENT_SWIPE = 0; /** Indicates that the Snackbar was dismissed via an action click.*/ public static final int DISMISS_EVENT_ACTION = 1; /** Indicates that the Snackbar was dismissed via a timeout.*/ public static final int DISMISS_EVENT_TIMEOUT = 2; /** Indicates that the Snackbar was dismissed via a call to {@link #dismiss()}.*/ public static final int DISMISS_EVENT_MANUAL = 3; /** Indicates that the Snackbar was dismissed from a new Snackbar being shown.*/ public static final int DISMISS_EVENT_CONSECUTIVE = 4; /** @hide */ @IntDef({DISMISS_EVENT_SWIPE, DISMISS_EVENT_ACTION, DISMISS_EVENT_TIMEOUT, DISMISS_EVENT_MANUAL, DISMISS_EVENT_CONSECUTIVE}) @Retention(RetentionPolicy.SOURCE) public @interface DismissEvent {} /** * Called when the given {@link Snackbar} has been dismissed, either through a time-out, * having been manually dismissed, or an action being clicked. * * @param snackbar The snackbar which has been dismissed. * @param event The event which caused the dismissal. One of either: * {@link #DISMISS_EVENT_SWIPE}, {@link #DISMISS_EVENT_ACTION}, * {@link #DISMISS_EVENT_TIMEOUT}, {@link #DISMISS_EVENT_MANUAL} or * {@link #DISMISS_EVENT_CONSECUTIVE}. * * @see Snackbar#dismiss() */ public void onDismissed(Snackbar snackbar, @DismissEvent int event) { // empty } /** * Called when the given {@link Snackbar} is visible. * * @param snackbar The snackbar which is now visible. * @see Snackbar#show() */ public void onShown(Snackbar snackbar) { // empty } } /** * @hide */ @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG}) @Retention(RetentionPolicy.SOURCE) public @interface Duration {} /** * Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time * that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown. * * @see #setDuration */ public static final int LENGTH_INDEFINITE = -2; /** * Show the Snackbar for a short period of time. * * @see #setDuration */ public static final int LENGTH_SHORT = -1; /** * Show the Snackbar for a long period of time. * * @see #setDuration */ public static final int LENGTH_LONG = 0; private static final int ANIMATION_DURATION = 250; private static final int ANIMATION_FADE_DURATION = 180; private static final Handler sHandler; private static final int MSG_SHOW = 0; private static final int MSG_DISMISS = 1; static { sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message message) { switch (message.what) { case MSG_SHOW: ((Snackbar) message.obj).showView(); return true; case MSG_DISMISS: ((Snackbar) message.obj).hideView(message.arg1); return true; } return false; } }); } private final ViewGroup mParent; private final Context mContext; private final SnackbarLayout mView; private int mDuration; private Callback mCallback; private Snackbar(ViewGroup parent) { mParent = parent; mContext = parent.getContext(); LayoutInflater inflater = LayoutInflater.from(mContext); mView = (SnackbarLayout) inflater.inflate(R.layout.design_layout_snackbar, mParent, false); } /** * Make a Snackbar to display a message * * <p>Snackbar will try and find a parent view to hold Snackbar's view from the value given * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, * which is defined as a {@link CoordinatorLayout} or the window decor's content view, * whichever comes first. * * </p><p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable * certain features, such as swipe-to-dismiss and automatically moving of widgets like * {@link FloatingActionButton}. * * @param view The view to find a parent from. * @param text The text to show. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link * #LENGTH_LONG} */ @NonNull public static Snackbar make(@NonNull View view, @NonNull CharSequence text, @Duration int duration) { Snackbar snackbar = new Snackbar(findSuitableParent(view)); snackbar.setText(text); snackbar.setDuration(duration); return snackbar; } /** * Make a Snackbar to display a message. * * </p><p>Snackbar will try and find a parent view to hold Snackbar's view from the value given * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, * which is defined as a {@link CoordinatorLayout} or the window decor's content view, * whichever comes first. * * </p><p>Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable * certain features, such as swipe-to-dismiss and automatically moving of widgets like * {@link FloatingActionButton}. * * @param view The view to find a parent from. * @param resId The resource id of the string resource to use. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link * #LENGTH_LONG} */ @NonNull public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) { return make(view, view.getResources().getText(resId), duration); } private static ViewGroup findSuitableParent(View view) { ViewGroup fallback = null; do { if (view instanceof CoordinatorLayout) { // We've found a CoordinatorLayout, use it return (ViewGroup) view; } else if (view instanceof FrameLayout) { if (view.getId() == android.R.id.content) { // If we've hit the decor content view, then we didn't find a CoL in the // hierarchy, so use it. return (ViewGroup) view; } else { // It's not the content view but we'll use it as our fallback fallback = (ViewGroup) view; } } if (view != null) { // Else, we will loop and crawl up the view hierarchy and try to find a parent final ViewParent parent = view.getParent(); view = parent instanceof View ? (View) parent : null; } } while (view != null); // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback return fallback; } /** * Set the action to be displayed in this {@link Snackbar}. * * @param resId String resource to display * @param listener callback to be invoked when the action is clicked */ @NonNull public Snackbar setAction(@StringRes int resId, View.OnClickListener listener) { return setAction(mContext.getText(resId), listener); } /** * Set the action to be displayed in this {@link Snackbar}. * * @param text Text to display * @param listener callback to be invoked when the action is clicked */ @NonNull public Snackbar setAction(CharSequence text, final View.OnClickListener listener) { final TextView tv = mView.getActionView(); if (TextUtils.isEmpty(text) || listener == null) { tv.setVisibility(View.GONE); tv.setOnClickListener(null); } else { tv.setVisibility(View.VISIBLE); tv.setText(text); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { listener.onClick(view); // Now dismiss the Snackbar dispatchDismiss(Callback.DISMISS_EVENT_ACTION); } }); } return this; } /** * Sets the text color of the action specified in * {@link #setAction(CharSequence, View.OnClickListener)}. */ @NonNull public Snackbar setActionTextColor(ColorStateList colors) { final TextView tv = mView.getActionView(); tv.setTextColor(colors); return this; } /** * Sets the text color of the action specified in * {@link #setAction(CharSequence, View.OnClickListener)}. */ @NonNull public Snackbar setActionTextColor(@ColorInt int color) { final TextView tv = mView.getActionView(); tv.setTextColor(color); return this; } /** * Update the text in this {@link Snackbar}. * * @param message The new text for the Toast. */ @NonNull public Snackbar setText(@NonNull CharSequence message) { final TextView tv = mView.getMessageView(); tv.setText(message); return this; } /** * Update the text in this {@link Snackbar}. * * @param resId The new text for the Toast. */ @NonNull public Snackbar setText(@StringRes int resId) { return setText(mContext.getText(resId)); } /** * Set how long to show the view for. * * @param duration either be one of the predefined lengths: * {@link #LENGTH_SHORT}, {@link #LENGTH_LONG}, or a custom duration * in milliseconds. */ @NonNull public Snackbar setDuration(@Duration int duration) { mDuration = duration; return this; } /** * Return the duration. * * @see #setDuration */ @Duration public int getDuration() { return mDuration; } /** * Returns the {@link Snackbar}'s view. */ @NonNull public View getView() { return mView; } /** * Show the {@link Snackbar}. */ public void show() { SnackbarManager.getInstance().show(mDuration, mManagerCallback); } /** * Dismiss the {@link Snackbar}. */ public void dismiss() { dispatchDismiss(Callback.DISMISS_EVENT_MANUAL); } private void dispatchDismiss(@Callback.DismissEvent int event) { SnackbarManager.getInstance().dismiss(mManagerCallback, event); } /** * Set a callback to be called when this the visibility of this {@link Snackbar} changes. */ @NonNull public Snackbar setCallback(Callback callback) { mCallback = callback; return this; } /** * Return whether this Snackbar is currently being shown. */ public boolean isShown() { return mView.isShown(); } private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() { @Override public void show() { sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this)); } @Override public void dismiss(int event) { sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this)); } }; final void showView() { if (mView.getParent() == null) { final ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (lp instanceof CoordinatorLayout.LayoutParams) { // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior final Behavior behavior = new Behavior(); behavior.setStartAlphaSwipeDistance(0.1f); behavior.setEndAlphaSwipeDistance(0.6f); behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END); behavior.setListener(new SwipeDismissBehavior.OnDismissListener() { @Override public void onDismiss(View view) { dispatchDismiss(Callback.DISMISS_EVENT_SWIPE); } @Override public void onDragStateChanged(int state) { switch (state) { case SwipeDismissBehavior.STATE_DRAGGING: case SwipeDismissBehavior.STATE_SETTLING: // If the view is being dragged or settling, cancel the timeout SnackbarManager.getInstance().cancelTimeout(mManagerCallback); break; case SwipeDismissBehavior.STATE_IDLE: // If the view has been released and is idle, restore the timeout SnackbarManager.getInstance().restoreTimeout(mManagerCallback); break; } } }); ((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior); } mParent.addView(mView); } if (ViewCompat.isLaidOut(mView)) { // If the view is already laid out, animate it now animateViewIn(); } else { // Otherwise, add one of our layout change listeners and animate it in when laid out mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() { @Override public void onLayoutChange(View view, int left, int top, int right, int bottom) { animateViewIn(); mView.setOnLayoutChangeListener(null); } }); } } private void animateViewIn() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { ViewCompat.setTranslationY(mView, mView.getHeight()); ViewCompat.animate(mView).translationY(0f) .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR) .setDuration(ANIMATION_DURATION) .setListener(new ViewPropertyAnimatorListenerAdapter() { @Override public void onAnimationStart(View view) { mView.animateChildrenIn(ANIMATION_DURATION - ANIMATION_FADE_DURATION, ANIMATION_FADE_DURATION); } @Override public void onAnimationEnd(View view) { if (mCallback != null) { mCallback.onShown(Snackbar.this); } SnackbarManager.getInstance().onShown(mManagerCallback); } }).start(); } else { Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_in); anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); anim.setDuration(ANIMATION_DURATION); anim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationEnd(Animation animation) { if (mCallback != null) { mCallback.onShown(Snackbar.this); } SnackbarManager.getInstance().onShown(mManagerCallback); } @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} }); mView.startAnimation(anim); } } private void animateViewOut(final int event) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { ViewCompat.animate(mView).translationY(mView.getHeight()) .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR) .setDuration(ANIMATION_DURATION) .setListener(new ViewPropertyAnimatorListenerAdapter() { @Override public void onAnimationStart(View view) { mView.animateChildrenOut(0, ANIMATION_FADE_DURATION); } @Override public void onAnimationEnd(View view) { onViewHidden(event); } }).start(); } else { Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_out); anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR); anim.setDuration(ANIMATION_DURATION); anim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationEnd(Animation animation) { onViewHidden(event); } @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} }); mView.startAnimation(anim); } } final void hideView(int event) { if (mView.getVisibility() != View.VISIBLE || isBeingDragged()) { onViewHidden(event); } else { animateViewOut(event); } } private void onViewHidden(int event) { // First remove the view from the parent mParent.removeView(mView); // Now call the dismiss listener (if available) if (mCallback != null) { mCallback.onDismissed(this, event); } // Finally, tell the SnackbarManager that it has been dismissed SnackbarManager.getInstance().onDismissed(mManagerCallback); } /** * @return if the view is being being dragged or settled by {@link SwipeDismissBehavior}. */ private boolean isBeingDragged() { final ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (lp instanceof CoordinatorLayout.LayoutParams) { final CoordinatorLayout.LayoutParams cllp = (CoordinatorLayout.LayoutParams) lp; final CoordinatorLayout.Behavior behavior = cllp.getBehavior(); if (behavior instanceof SwipeDismissBehavior) { return ((SwipeDismissBehavior) behavior).getDragState() != SwipeDismissBehavior.STATE_IDLE; } } return false; } /** * @hide */ public static class SnackbarLayout extends LinearLayout { private TextView mMessageView; private Button mActionView; private int mMaxWidth; private int mMaxInlineActionWidth; interface OnLayoutChangeListener { public void onLayoutChange(View view, int left, int top, int right, int bottom); } private OnLayoutChangeListener mOnLayoutChangeListener; public SnackbarLayout(Context context) { this(context, null); } public SnackbarLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout); mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1); mMaxInlineActionWidth = a.getDimensionPixelSize( R.styleable.SnackbarLayout_maxActionInlineWidth, -1); if (a.hasValue(R.styleable.SnackbarLayout_elevation)) { ViewCompat.setElevation(this, a.getDimensionPixelSize( R.styleable.SnackbarLayout_elevation, 0)); } a.recycle(); setClickable(true); // Now inflate our content. We need to do this manually rather than using an <include> // in the layout since older versions of the Android do not inflate includes with // the correct Context. LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include, this); } @Override protected void onFinishInflate() { super.onFinishInflate(); mMessageView = (TextView) findViewById(R.id.snackbar_text); mActionView = (Button) findViewById(R.id.snackbar_action); } TextView getMessageView() { return mMessageView; } Button getActionView() { return mActionView; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } final int multiLineVPadding = getResources().getDimensionPixelSize( R.dimen.design_snackbar_padding_vertical_2lines); final int singleLineVPadding = getResources().getDimensionPixelSize( R.dimen.design_snackbar_padding_vertical); final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1; boolean remeasure = false; if (isMultiLine && mMaxInlineActionWidth > 0 && mActionView.getMeasuredWidth() > mMaxInlineActionWidth) { if (updateViewsWithinLayout(VERTICAL, multiLineVPadding, multiLineVPadding - singleLineVPadding)) { remeasure = true; } } else { final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding; if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) { remeasure = true; } } if (remeasure) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } void animateChildrenIn(int delay, int duration) { ViewCompat.setAlpha(mMessageView, 0f); ViewCompat.animate(mMessageView).alpha(1f).setDuration(duration) .setStartDelay(delay).start(); if (mActionView.getVisibility() == VISIBLE) { ViewCompat.setAlpha(mActionView, 0f); ViewCompat.animate(mActionView).alpha(1f).setDuration(duration) .setStartDelay(delay).start(); } } void animateChildrenOut(int delay, int duration) { ViewCompat.setAlpha(mMessageView, 1f); ViewCompat.animate(mMessageView).alpha(0f).setDuration(duration) .setStartDelay(delay).start(); if (mActionView.getVisibility() == VISIBLE) { ViewCompat.setAlpha(mActionView, 1f); ViewCompat.animate(mActionView).alpha(0f).setDuration(duration) .setStartDelay(delay).start(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed && mOnLayoutChangeListener != null) { mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b); } } void setOnLayoutChangeListener(OnLayoutChangeListener onLayoutChangeListener) { mOnLayoutChangeListener = onLayoutChangeListener; } private boolean updateViewsWithinLayout(final int orientation, final int messagePadTop, final int messagePadBottom) { boolean changed = false; if (orientation != getOrientation()) { setOrientation(orientation); changed = true; } if (mMessageView.getPaddingTop() != messagePadTop || mMessageView.getPaddingBottom() != messagePadBottom) { updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom); changed = true; } return changed; } private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) { if (ViewCompat.isPaddingRelative(view)) { ViewCompat.setPaddingRelative(view, ViewCompat.getPaddingStart(view), topPadding, ViewCompat.getPaddingEnd(view), bottomPadding); } else { view.setPadding(view.getPaddingLeft(), topPadding, view.getPaddingRight(), bottomPadding); } } } final class Behavior extends SwipeDismissBehavior<snackbarlayout> { @Override public boolean onInterceptTouchEvent(CoordinatorLayout parent, SnackbarLayout child, MotionEvent event) { // We want to make sure that we disable any Snackbar timeouts if the user is // currently touching the Snackbar. We restore the timeout when complete if (parent.isPointInChildBounds(child, (int) event.getX(), (int) event.getY())) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: SnackbarManager.getInstance().cancelTimeout(mManagerCallback); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: SnackbarManager.getInstance().restoreTimeout(mManagerCallback); break; } } return super.onInterceptTouchEvent(parent, child, event); } } }</snackbarlayout></include></p>
添加語音處理能力(Adding Voice Capabilities)語音操作是可穿戴用戶體驗的重要部分,可以讓用戶以快捷、免提的方式執行動作。Wear提供兩種類型的語音
Android L: Google已經確認Android L就是Android Lollipop(5
簡介本篇是接上一篇seekbar的自定義view進階版。本自定義view主要功能:可自定義起始時間以及最大時間,設置總格數,每格均分時間差。 可自定義界面顏色字體大小,文
目前智能電視終端(智能電視和智能電視盒子)已經越來越火,過去主打視頻功能,如今的智能電視終端不僅會繼續完善視頻功能,還會加入電視游戲功能,同時這也趕上了“電視游戲機解禁”