SweetTips: 快意靈動的Android提示庫!

此文章是我在簡書的文章,自行搬到開源中國.簡書地址:SweetTips: 快意靈動的Android提示庫!java

源碼及所在DEMO已上傳至GitHub:SweetTips,歡迎你們提Bug,喜歡的話記得Star或Fork下哈!android

##1.爲何要寫這個庫? 上面的問題也能夠這樣問:有哪些常見的需求,Android原生Toast及Design包中的Snackbar實現起來相對繁瑣? Toast:git

  1. 原生Toast沒法/不方便自定義顯示時間;
  2. 原生Toast,須要等待隊列中前面的Toast實例顯示完畢以後才能夠顯示,實時性差;
  3. 原生Toast,想在正在顯示的Toast實例上顯示新的內容並設置新內容的顯示時間,實現較繁瑣;
  4. 原生Toast,沒法/不方便自定義動畫;
  5. Android系統版本過多,不一樣的廠商對系統的定製也很不一樣,同一段代碼在不一樣的機器上,Toast的樣式差別很大,不利於App的一致性體驗;

Snackbar:github

  • Design包中的Snackbar,沒法自定義動畫;

##2.SweetTips有什麼用? 很顯然,能夠解決上面列舉的那些很常見的小問題;ide

截圖:工具

SweetToast及SweetSnackbar效果錄屏.gif

##3.SweetTips的結構? 自定義Toast:SweetToast + 自定義Snackbar:SweetSnackbar + SnackbarUtils:SweetSnackbar的工具類post

##4.SweetTips的實現思路 SweetToast:動畫

  • 在SweetToastManager中,利用隊列實現對SweetToast實例的管理,直接調用SweetToast的show()方法,能夠實現和原生Toast幾乎一致的體驗;
  • 在SweetToastManager中,經過對隊列的清空,實現即時顯示當前SweetToast實例的內容;
  • 在SweetToast中,經過設置WindowManager.LayoutParams.windowAnimations,實現SweetToast實例自定義的出入場動畫;
  • SweetToast支持鏈式調用,調用盡量的快捷;

SweetSnackbar:ui

  • 幾乎徹底拷貝了Design包中的Snackbar,只是添加了一個設置自定義出入場動畫的方法:setAnimations
  • 參照以前寫過的一個工具類GitHub:SnackbarUtils,爲SweetSnackbar也寫了一個工具類,一樣支持練市調用,實現'一行代碼設置多重屬性';

SweetTips.javathis

  • 這個工具類待完善,是爲了經過SweetToast或SweetSnackbar,封裝一些比較經常使用且精美的效果,經過靜態方法直接調用,提高開發者一些效率.

另外,爲了這個提示庫,也花了很多時間收集了一些經常使用的顏色,保存在Constant.java中,可做爲一個通用的工具類適用於不一樣項目,喜歡的同窗儘管拿走.

##5.SweetTips的使用限制 SweetToast是經過WindowManager向屏幕添加View來展現提示信息:

params.type = WindowManager.LayoutParams.TYPE_TOAST;

在Manifest.xml中已經聲明過權限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

在SDK>=23(Android 6)的系統中,用戶須要手動容許當前App使用這個權限,才能夠正常顯示!

##6.SweetTips部分代碼

/**
 * 自定義Toast
 *
 * 做者:幻海流心
 * GitHub:https://github.com/HuanHaiLiuXin
 * 郵箱:wall0920@163.com
 * 2016/12/13
 */

public final class SweetToast {
    public static final int LENGTH_SHORT = 0;
    public static final int LENGTH_LONG = 1;
    public static final long SHORT_DELAY = 2000; // 2 seconds
    public static final long LONG_DELAY = 3500; // 3.5 seconds
    //SweetToast默認背景色
    private static int mBackgroundColor = 0XE8484848;
    //
    private View mContentView = null;   //內容區域View
    private SweetToastConfiguration mConfiguration = null;
    private WindowManager mWindowManager = null;
    private boolean showing = false;    //是否在展現中
    private boolean showEnabled = true; //是否容許展現
    private boolean hideEnabled = true; //是否容許移除
    private boolean stateChangeEnabled = true;  //是否容許改變展現狀態

    public static SweetToast makeText(Context context, CharSequence text){
        return makeText(context, text, LENGTH_SHORT);
    }
    public static SweetToast makeText(View mContentView){
        return makeText(mContentView, LENGTH_SHORT);
    }
    public static SweetToast makeText(Context context, CharSequence text, int duration) {
        try {
            LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View v = inflate.inflate(R.layout.transient_notification, null);
            TextView tv = (TextView)v.findViewById(R.id.message);
            tv.setText(text);
            SweetToast sweetToast = new SweetToast();
            sweetToast.mContentView = v;
            sweetToast.mContentView.setBackgroundDrawable(getBackgroundDrawable(sweetToast, mBackgroundColor));
            initConfiguration(sweetToast,duration);
            return sweetToast;
        }catch (Exception e){
            Log.e("幻海流心","e:"+e.getLocalizedMessage()+":69");
        }
        return null;
    }
    public static SweetToast makeText(View mContentView, int duration){
        SweetToast sweetToast = new SweetToast();
        sweetToast.mContentView = mContentView;
        initConfiguration(sweetToast,duration);
        return sweetToast;
    }
    private static void initConfiguration(SweetToast sweetToast,int duration){
        try {
            if(duration < 0){
                throw new RuntimeException("顯示時長必須>=0!");
            }
            //1:初始化mWindowManager
            sweetToast.mWindowManager = (WindowManager) sweetToast.getContentView().getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            //2:初始化mConfiguration
            SweetToastConfiguration mConfiguration = new SweetToastConfiguration();
            //2.1:設置顯示時間
            mConfiguration.setDuration(duration);
            //2.2:設置WindowManager.LayoutParams屬性
            WindowManager.LayoutParams params = new WindowManager.LayoutParams();
            final Configuration config = sweetToast.getContentView().getContext().getResources().getConfiguration();
            final int gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
            params.gravity = gravity;
            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                params.horizontalWeight = 1.0f;
            }
            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                params.verticalWeight = 1.0f;
            }
            params.x = 0;
            params.y = sweetToast.getContentView().getContext().getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
            params.verticalMargin = 0.0f;
            params.horizontalMargin = 0.0f;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = R.style.Anim_SweetToast;
            //在小米5S上實驗,前兩種type均會報錯
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
//            params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
//            params.type = WindowManager.LayoutParams.TYPE_PHONE;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            mConfiguration.setParams(params);
            sweetToast.setConfiguration(mConfiguration);
        }catch (Exception e){
            Log.e("幻海流心","e:"+e.getLocalizedMessage()+":120");
        }
    }
    /**
     * 根據指定的背景色,得到mToastView的背景drawable實例
     * @param backgroundColor
     * @return
     */
    private static ShapeDrawable getBackgroundDrawable(SweetToast sweetToast, @ColorInt int backgroundColor){
        try {
            ShapeDrawable shapeDrawable = new ShapeDrawable();
            DrawableCompat.setTint(shapeDrawable,backgroundColor);
            //獲取當前設備的屏幕尺寸
            //實驗發現不一樣的設備上面,Toast內容區域的padding值並不相同,根據屏幕的寬度分別進行處理,儘可能接近設備原生Toast的體驗
            int widthPixels = sweetToast.getContentView().getResources().getDisplayMetrics().widthPixels;
            int heightPixels = sweetToast.getContentView().getResources().getDisplayMetrics().heightPixels;
            float density = sweetToast.getContentView().getResources().getDisplayMetrics().density;
            if(widthPixels >= 1070){
                //例如小米5S:1920 x 1080
                shapeDrawable.setPadding((int)(density*13),(int)(density*12),(int)(density*13),(int)(density*12));
            }else {
                //例如紅米2:1280x720
                shapeDrawable.setPadding((int)(density*14),(int)(density*13),(int)(density*14),(int)(density*13));
            }
            float radius = density*8;
            float[] outerRadii = new float[]{radius,radius,radius,radius,radius,radius,radius,radius};
            int width = sweetToast.getContentView().getWidth();
            int height = sweetToast.getContentView().getHeight();
            RectF rectF = new RectF(1,1,width-1,height-1);
            RoundRectShape roundRectShape = new RoundRectShape(outerRadii,rectF,null);
            shapeDrawable.setShape(roundRectShape);
            DrawableCompat.setTint(shapeDrawable,backgroundColor);
            return shapeDrawable;
        }catch (Exception e){
            Log.e("幻海流心","e:"+e.getLocalizedMessage()+":154");
        }
        return null;
    }
    /**
     * 自定義SweetToast實例的入場出場動畫
     * @param windowAnimations
     * @return
     */
    public SweetToast setWindowAnimations(@StyleRes int windowAnimations){
        mConfiguration.getParams().windowAnimations = windowAnimations;
        return this;
    }
    public SweetToast setGravity(int gravity, int xOffset, int yOffset) {
        mConfiguration.getParams().gravity = gravity;
        mConfiguration.getParams().x = xOffset;
        mConfiguration.getParams().y = yOffset;
        return this;
    }
    public SweetToast setMargin(float horizontalMargin, float verticalMargin) {
        mConfiguration.getParams().horizontalMargin = horizontalMargin;
        mConfiguration.getParams().verticalMargin = verticalMargin;
        return this;
    }
    /**
     * 向mContentView中添加View
     *
     * @param view
     * @param index
     * @return
     */
    public SweetToast addView(View view, int index) {
        if(mContentView != null && mContentView instanceof ViewGroup){
            ((ViewGroup)mContentView).addView(view,index);
        }
        return this;
    }
    /**
     * 設置SweetToast實例中TextView的文字顏色
     *
     * @param messageColor
     * @return
     */
    public SweetToast messageColor(@ColorInt int messageColor){
        if(mContentView !=null && mContentView.findViewById(R.id.message) != null && mContentView.findViewById(R.id.message) instanceof TextView){
            TextView textView = ((TextView) mContentView.findViewById(R.id.message));
            textView.setTextColor(messageColor);
        }
        return this;
    }
    /**
     * 設置SweetToast實例的背景顏色
     *
     * @param backgroundColor
     * @return
     */
    public SweetToast backgroundColor(@ColorInt int backgroundColor){
        if(mContentView!=null){
            mContentView.setBackgroundDrawable(getBackgroundDrawable(this, backgroundColor));
        }
        return this;
    }
    /**
     * 設置SweetToast實例的背景資源
     *
     * @param background
     * @return
     */
    public SweetToast backgroundResource(@DrawableRes int background){
        if(mContentView!=null){
            mContentView.setBackgroundResource(background);
        }
        return this;
    }
    /**
     * 設置SweetToast實例的文字顏色及背景顏色
     *
     * @param messageColor
     * @param backgroundColor
     * @return
     */
    public SweetToast colors(@ColorInt int messageColor, @ColorInt int backgroundColor) {
        messageColor(messageColor);
        backgroundColor(backgroundColor);
        return this;
    }
    /**
     * 設置SweetToast實例的文字顏色及背景資源
     *
     * @param messageColor
     * @param background
     * @return
     */
    public SweetToast textColorAndBackground(@ColorInt int messageColor, @DrawableRes int background) {
        messageColor(messageColor);
        backgroundResource(background);
        return this;
    }

    /**
     * 設置SweetToast實例的寬高
     *  頗有用的功能,參考了簡書上的文章:http://www.jianshu.com/p/491b17281c0a
     * @param width     SweetToast實例的寬度,單位是pix
     * @param height    SweetToast實例的高度,單位是pix
     * @return
     */
    public SweetToast size(int width, int height){
        if(mContentView!=null && mContentView instanceof LinearLayout){
            mContentView.setMinimumWidth(width);
            mContentView.setMinimumHeight(height);
            ((LinearLayout)mContentView).setGravity(Gravity.CENTER);
            try {
                TextView textView = ((TextView) mContentView.findViewById(R.id.message));
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
                params.width = LinearLayout.LayoutParams.MATCH_PARENT;
                params.height = LinearLayout.LayoutParams.MATCH_PARENT;
                textView.setLayoutParams(params);
                textView.setGravity(Gravity.CENTER);
            }catch (Exception e){
                Log.e("幻海流心","e:"+e.getLocalizedMessage());
            }
        }
        return this;
    }

    /**
     * 設置SweetToast實例的顯示位置:左上
     * @return
     */
    public SweetToast leftTop(){
        return setGravity(Gravity.LEFT|Gravity.TOP,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:右上
     * @return
     */
    public SweetToast rightTop(){
        return setGravity(Gravity.RIGHT|Gravity.TOP,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:左下
     * @return
     */
    public SweetToast leftBottom(){
        return setGravity(Gravity.LEFT|Gravity.BOTTOM,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:右下
     * @return
     */
    public SweetToast rightBottom(){
        return setGravity(Gravity.RIGHT|Gravity.BOTTOM,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:上中
     * @return
     */
    public SweetToast topCenter(){
        return setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:下中
     * @return
     */
    public SweetToast bottomCenter(){
        return setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:左中
     * @return
     */
    public SweetToast leftCenter(){
        return setGravity(Gravity.LEFT|Gravity.CENTER_VERTICAL,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:右中
     * @return
     */
    public SweetToast rightCenter(){
        return setGravity(Gravity.RIGHT|Gravity.CENTER_VERTICAL,0,0);
    }
    /**
     * 設置SweetToast實例的顯示位置:正中
     * @return
     */
    public SweetToast center(){
        return setGravity(Gravity.CENTER,0,0);
    }
    /**
     * 將SweetToast實例顯示在指定View的頂部
     * @param targetView    指定View
     * @param statusHeight  狀態欄顯示狀況下,狀態欄的高度
     * @return
     */
    public SweetToast layoutAbove(View targetView, int statusHeight){
        if(mContentView!=null){
            int[] locations = new int[2];
            targetView.getLocationOnScreen(locations);
            //必須保證指定View的頂部可見
            int screenHeight = ScreenUtil.getScreenHeight(mContentView.getContext());
            if(locations[1] > statusHeight&&locations[1]<screenHeight){
                setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL,0,screenHeight - locations[1]);
            }
        }
        return this;
    }
    /**
     * 將SweetToast實例顯示在指定View的底部
     * @param targetView
     * @param statusHeight
     * @return
     */
    public SweetToast layoutBellow(View targetView, int statusHeight){
        if(mContentView!=null){
            int[] locations = new int[2];
            targetView.getLocationOnScreen(locations);
            //必須保證指定View的底部可見
            int screenHeight = ScreenUtil.getScreenHeight(mContentView.getContext());
            if(locations[1]+targetView.getHeight() > statusHeight&&locations[1]+targetView.getHeight()<screenHeight){
                setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL,0,locations[1]+targetView.getHeight()-statusHeight);
            }
        }
        return this;
    }


    /**********************************************  SweetToast顯示及移除  **********************************************/
    Handler mHandler = new Handler();
    Runnable mHide = new Runnable() {
        @Override
        public void run() {
            handleHide();
        }
    };
    protected void handleHide() {
        if(this != null && mContentView != null){
            if(stateChangeEnabled){
                if(hideEnabled){
                    if(showing){
                        mWindowManager.removeView(mContentView);
                    }
                    showing = false;
                    mContentView = null;
                }else{
                }
            }
        }
    }
    protected void handleShow() {
        if(mContentView != null){
            if(stateChangeEnabled){
                if(showEnabled){
                    try {
                        mWindowManager.addView(mContentView,mConfiguration.getParams());
                        long delay = (mConfiguration.getDuration() == LENGTH_LONG || mConfiguration.getDuration() == Toast.LENGTH_LONG) ? LONG_DELAY : ((mConfiguration.getDuration() == LENGTH_SHORT || mConfiguration.getDuration() == Toast.LENGTH_SHORT)? SHORT_DELAY : mConfiguration.getDuration());
                        mHandler.postDelayed(mHide,delay);
                        showing = true;
                    }catch (Exception e){
                        Log.e("幻海流心","e:"+e.getLocalizedMessage()+":213");
                    }
                }
            }
        }
    }

    /**
     * 保持當前實例的顯示狀態:不容許向Window中添加或者移除View
     */
    protected void removeCallbacks(){
        stateChangeEnabled = false;
    }

    /**
     * 設置是否容許展現當前實例
     * @param showEnabled
     */
    public void setShowEnabled(boolean showEnabled) {
        this.showEnabled = showEnabled;
    }

    /**
     * 設置是否容許移除當前實例中的View
     * @param hideEnabled
     */
    public void setHideEnabled(boolean hideEnabled) {
        this.hideEnabled = hideEnabled;
    }

    /**
     * 設置是否容許改變當前實例的展現狀態
     * @param stateChangeEnabled
     */
    public void setStateChangeEnabled(boolean stateChangeEnabled) {
        this.stateChangeEnabled = stateChangeEnabled;
    }

    /**
     * 將當前實例添加到隊列{@link SweetToastManager#queue}中,若隊列爲空,則加入隊列後直接進行展現
     */
    public void show(){
        try {
            if (Build.VERSION.SDK_INT >= 23) {
                //Android6.0以上,須要動態聲明權限
                if(mContentView!=null && !Settings.canDrawOverlays(mContentView.getContext().getApplicationContext())) {
                    //用戶還未容許該權限
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    mContentView.getContext().startActivity(intent);
                    return;
                } else if(mContentView!=null) {
                    //用戶已經容許該權限
                    SweetToastManager.show(this);
                }
            } else {
                //Android6.0如下,不用動態聲明權限
                if (mContentView!=null) {
                    SweetToastManager.show(this);
                }
            }
//            SweetToastManager.show(this);
        }catch (Exception e){
            Log.e("幻海流心","e:"+e.getLocalizedMessage()+":232");
        }
    }
    /**
     * 利用隊列{@link SweetToastManager#queue}中正在展現的SweetToast實例,繼續展現當前實例的內容
     */
    public void showByPrevious(){
        try {
            if (Build.VERSION.SDK_INT >= 23) {
                //Android6.0以上,須要動態聲明權限
                if(mContentView!=null && !Settings.canDrawOverlays(mContentView.getContext().getApplicationContext())) {
                    //用戶還未容許該權限
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    mContentView.getContext().startActivity(intent);
                    return;
                } else if(mContentView!=null) {
                    //用戶已經容許該權限
                    SweetToastManager.showByPrevious(this);
                }
            } else {
                //Android6.0如下,不用動態聲明權限
                if (mContentView!=null) {
                    SweetToastManager.showByPrevious(this);
                }
            }
//            SweetToastManager.showByPrevious(this);
        }catch (Exception e){
            Log.e("幻海流心","e:"+e.getLocalizedMessage()+":290");
        }
    }
    /**
     * 清空隊列{@link SweetToastManager#queue}中已經存在的SweetToast實例,直接展現當前實例的內容
     */
    public void showImmediate(){
        try {
            if (Build.VERSION.SDK_INT >= 23) {
                //Android6.0以上,須要動態聲明權限
                if(mContentView!=null && !Settings.canDrawOverlays(mContentView.getContext().getApplicationContext())) {
                    //用戶還未容許該權限
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    mContentView.getContext().startActivity(intent);
                    return;
                } else if(mContentView!=null) {
                    //用戶已經容許該權限
                    SweetToastManager.showImmediate(this);
                }
            } else {
                //Android6.0如下,不用動態聲明權限
                if (mContentView!=null) {
                    SweetToastManager.showImmediate(this);
                }
            }
//            SweetToastManager.showImmediate(this);
        }catch (Exception e){
            Log.e("幻海流心","e:"+e.getLocalizedMessage()+":252");
        }
    }
    /**
     * 移除當前SweetToast並將mContentView置空
     */
    public void hide() {
        mHandler.post(mHide);
    }
    /**********************************************  SweetToast顯示及移除  **********************************************/

    //Setter&Getter
    public View getContentView() {
        return mContentView;
    }
    public void setContentView(View mContentView) {
        this.mContentView = mContentView;
    }
    public SweetToastConfiguration getConfiguration() {
        return mConfiguration;
    }
    public void setConfiguration(SweetToastConfiguration mConfiguration) {
        this.mConfiguration = mConfiguration;
    }
    public WindowManager getWindowManager() {
        return mWindowManager;
    }
    public void setWindowManager(WindowManager mWindowManager) {
        this.mWindowManager = mWindowManager;
    }
    public boolean isShowing() {
        return showing;
    }
    public void setShowing(boolean showing) {
        this.showing = showing;
    }
}

源碼及所在DEMO已上傳至GitHub:SweetTips,歡迎你們提Bug,喜歡的話記得Star或Fork下哈!

That's all !

相關文章
相關標籤/搜索