Android 自定義Toast:輕松實現(xiàn)深度可定制化的Toast

前言

用過系統(tǒng)自帶的Toast的都知道,android自帶的吐司比較難看,而且樣式單一,最重要的是不能自由選擇動畫,這樣的吐司很難用在一個精美的應(yīng)用上,因此,我們來自行實現(xiàn)一個自定義的Toast:XToast,來取代系統(tǒng)自帶的Toast。當然了,以下實現(xiàn)的自定義Toast是與系統(tǒng)Toast有區(qū)別的,下面會提到。

使用方式&預(yù)覽

我們來看看怎么使用XToast,通過提供的一系列的set方法,可自行設(shè)置XToast的各種屬性、樣式等:

XToast.create(MainActivity.this)
        .setText("Testing:This is a XToast....")
        .setAnimation(AnimationUtils.ANIMATION_DRAWER) //抽屜式效果
        .setDuration(XToast.XTOAST_SHORT)
        .setOnDisappearListener(new XToast.OnDisappearListener() {
            @Override
            public void onDisappear(XToast xToast) {
                Log.d("cylog","The XToast has disappeared..");
            }
        }).show();

接著這是實現(xiàn)的效果:
首先這是一個抽屜式效果的Toast:


抽屜式效果.gif

這是一個縮放效果的Toast:

縮放式土司.gif

原理簡述

其實實現(xiàn)原理很簡單,是通過添加View來實現(xiàn)的,因為Toast實際上就是一個View,通過控制、修改View的不同屬性,然后通過不同的動畫效果使其更加生動絢麗地展現(xiàn)出來。

實現(xiàn)

首先,我們新建名為XToast.java文件,這也是我們的主要的類,

public class XToast  {

    private Context mContext;
    private View mView;
    private ViewGroup mViewGroup;
    private ViewGroup mViewRoot;
    private GradientDrawable mToastBackgound;
    private LayoutInflater mInflater;
    private TextView mTextView;
    private String message;
    private AnimatorSet mShowAnimatorSet;
    private AnimatorSet mHideAnimatorSet;
    private int mShowAnimationType;
    private int mHideAnimationType;
    private int mDuration;
    private int mBackgroundColor;
    private OnDisappearListener mOnDisappearListener;

    public static final int XTOAST_LONG = 3500;
    public static final int XTOAST_SHORT = 2000;

    public interface OnDisappearListener{
        void onDisappear(XToast xToast);
    }

    public XToast(Context context)
    {
        this.mContext = context;
        this.mInflater = LayoutInflater.from(context);
    }

    public XToast(Context context,String message)
    {
        this.mContext = context;
        this.message = message;
        this.mInflater = LayoutInflater.from(context);
    }

    public static XToast create(Context context){
        return new XToast(context);
    }

    public static XToast create(Context context,String message){
        return new XToast(context,message);
    }

    //省略一系列的set和get方法
    //...
    public boolean isShowing() {
        return mView != null && mView.isShown();
    }

    private void initViews() {
        mViewRoot = (ViewGroup) ((Activity)mContext).findViewById(android.R.id.content);
        mViewGroup = new LinearLayout(mContext);
        FrameLayout.LayoutParams mViewGroupParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
        mViewGroupParams.gravity = Gravity.BOTTOM | Gravity.CENTER;
        mViewGroupParams.bottomMargin = 200;
        mViewGroup.setLayoutParams(mViewGroupParams);
        mViewRoot.addView(mViewGroup);

        //如果用戶沒有使用自己的View,那么使用默認的mView
        if(mView == null){
            mView = mInflater.inflate(R.layout.xtoast_normal,mViewGroup,false);
            mToastBackgound = (GradientDrawable) mView.getBackground();
            mTextView = (TextView) mView.findViewById(R.id.message);
            mTextView.setText(message);
            if(mBackgroundColor != 0){
                mToastBackgound.setColor(mBackgroundColor);
            }
        }

        //對mView的大小進行測量
        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30) -1, View.MeasureSpec.AT_MOST);
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30) -1,View.MeasureSpec.AT_MOST);
        mView.measure(widthMeasureSpec,heightMeasureSpec);

    }

    public void show(){

        //準備工作
        initViews();
        if(this.mShowAnimationType == 0)    
          this.mShowAnimatorSet = AnimationUtils.getShowAnimation(this,AnimationUtils.ANIMATION_DEFAULT);
        else    
          this.mShowAnimatorSet = AnimationUtils.getShowAnimation(this,mShowAnimationType);
        if(this.mHideAnimationType == 0)    
          this.mHideAnimatorSet = AnimationUtils.getHideAnimation(this, AnimationUtils.ANIMATION_DEFAULT);
        else    
          this.mHideAnimatorSet = AnimationUtils.getHideAnimation(this,mHideAnimationType);

        if(mDuration == 0)
            mDuration = XTOAST_SHORT;

        XToastHandler xToastHandler = XToastHandler.getInstance();
        xToastHandler.add(this);
    }
}

我們主要關(guān)注initViews()方法,在方法內(nèi)部,我們首先通過android.R.id.content來獲得一個ViewGroup的實例,這個是DecorView內(nèi)部的content布局,關(guān)于DecorView的內(nèi)容讀者可參考我之前的文章。這里簡單說明一下:DecorView是我們Activity的根布局,是Activity初始化后生成的,而我們通過setContentView()方法添加的布局會添加到DecorView之內(nèi),而上面代碼我們通過findViewById(android.R.id.content)獲得了DecorView的一個子元素(名為content的布局),在后面,我們會把相應(yīng)的View都添加到這個布局里面。我們繼續(xù)往下看:接著新建了一個LinearLayout布局,展現(xiàn)消息的View就是添加到該LinearLayout布局之內(nèi)的,然后把LinearLayout添加到DecorView中,換句話來說:展現(xiàn)消息的View外面包裹了一層LinearLayout。但是,為什么要包裹一層LinearLayout呢?為什么不直接把View添加到DecorView呢?其實,這樣做的目的是為了實現(xiàn)抽屜式的動畫效果。我們只需要對mView的translationY屬性進行操作就能輕松實現(xiàn)抽屜式效果了,這樣非常方便。

我們接著看show()方法,主要是進行一些準備工作,比如對動畫效果的設(shè)置、消息時長的設(shè)置等,接著我們可以看到用了XToastHandler這個類,那么我們看看這個XToastHandler.java

public class XToastHandler extends Handler {
    private static XToastHandler mToastHandler;
    private final Queue<XToast> mQueue;

    private final static int SHOW_TOAST = 0x123;
    private final static int HIDE_TOAST = 0x456;
    private final static int SHOWNEXT_TOAST = 0X789;

    private XToastHandler()
    {
        mQueue = new LinkedList<>();
    }

    public synchronized static XToastHandler getInstance()
    {
        if(mToastHandler != null)
            return mToastHandler;
        else{
            mToastHandler = new XToastHandler();
            return mToastHandler;
        }
    }

    /**
     *  該方法把XToast添加到消息隊列中
     * @param xToast
     */
    public void add(XToast xToast)
    {
        mQueue.offer(xToast);
        showNextToast();
    }
    
    public void showNextToast()
    {
        if(mQueue.isEmpty()) return;
        //獲取隊列頭部的XToast
        XToast xToast = mQueue.peek();
        if(!xToast.isShowing()){
            Message message = Message.obtain();
            message.what = SHOW_TOAST;
            message.obj = xToast;
            sendMessage(message);
        }
    }

    @Override
    public void handleMessage(Message msg) {
        XToast xToast = (XToast) msg.obj;
        switch (msg.what)
        {
            case SHOW_TOAST:
                showToast(xToast);
                break;

            case HIDE_TOAST:
                hideToast(xToast);
                break;

            case SHOWNEXT_TOAST:
                showNextToast();
                break;
        }
    }

    private void hideToast(final XToast xToast) {
        AnimatorSet set = xToast.getHideAnimatorSet();
        set.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //如果動畫結(jié)束了,移除隊列頭部元素以及從界面中移除mView
                xToast.getViewGroup().removeView(xToast.getView());
                xToast.getOnDisappearListener().onDisappear(xToast);
                mQueue.poll();
                sendEmptyMessage(SHOWNEXT_TOAST);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        set.start();
    }

    private void showToast(XToast xToast) {
        
        //如果當前有XToast正在展示,直接返回
        if(xToast.isShowing()) return;
        //把mView添加到界面中,實現(xiàn)Toast效果
        xToast.getViewGroup().addView(xToast.getView());
        //獲取動畫效果
        AnimatorSet set = xToast.getShowAnimatorSet();
        set.start();

        Message message = Message.obtain();
        message.what = HIDE_TOAST;
        message.obj = xToast;
        sendMessageDelayed(message,xToast.getDuration());
    }
}

這里把XToastHandler設(shè)計成單例模式,目的是為了維護同一個XToast的消息隊列。實現(xiàn)過程也非常簡單,通過新建一個LinkedList來儲存需要展現(xiàn)的Toast,該LinkedList是一個隊列,當一個消息展示完畢后,我們會把該消息移除,下一個消息才能繼續(xù)顯示,這也與系統(tǒng)的Toast相類似,同一段時間內(nèi)只會顯示一條Toast。

最后我們再來看看AnimationUtils這個類,該類儲存了幾種不同的動畫效果:

public class AnimationUtils {

    public static final int ANIMATION_DEFAULT = 0X000;
    public static final int ANIMATION_DRAWER = 0x001;
    public static final int ANIMATION_SCALE = 0x002;

    public static AnimatorSet getShowAnimation(XToast xToast,int animationType){
        switch (animationType){
            case ANIMATION_DRAWER:
                AnimatorSet drawerSet = new AnimatorSet();
                drawerSet.playTogether(
                        ObjectAnimator.ofFloat(xToast.getView(), "translationY", -xToast.getView().getMeasuredHeight(), 0),
                        ObjectAnimator.ofFloat(xToast.getView(), "alpha", 0, 1)
                );
                drawerSet.setDuration(500);
                return drawerSet;

            case ANIMATION_SCALE:
                AnimatorSet scaleSet = new AnimatorSet();
                scaleSet.playTogether(
                        ObjectAnimator.ofFloat(xToast.getView(), "scaleX", 0, 1),
                        ObjectAnimator.ofFloat(xToast.getView(), "scaleY", 0, 1)
                );
                scaleSet.setDuration(500);
                return scaleSet;

            default:
                AnimatorSet defaultSet = new AnimatorSet();
                defaultSet.play(ObjectAnimator.ofFloat(xToast.getView(), "alpha", 0, 1));
                defaultSet.setDuration(500);
                return defaultSet;
        }
    }

    //省略Hide動畫...
    //...

}

可見,實現(xiàn)方式也比較簡單,就是直接對mView的某個屬性進行屬性動畫,比如抽屜式效果的話,我們直接操作它的translationY屬性,那么它在它的父布局(上面提到的LinearLayout)的位置就會得到改變,也就實現(xiàn)了抽屜效果。假如,這里mView沒有包裹一層布局的話,而是直接添加到DecorView中,那么直接操作translationY屬性則得不到抽屜式效果,這個讀者可自行驗證。

那么到目前為止,該XToast也介紹得差不多了。最后再談?wù)勊膶崿F(xiàn)與系統(tǒng)Toast有什么不同之處,其實XToast是依賴于Activity而存在的,因為是把mView添加到了DecorView中,如果沒有Activity則無法使用XToast,這也是不足之處,而系統(tǒng)Toast是獨立的一個窗口,不依賴于任何組件。

最后,貼上該XToast的GitHub地址:https://github.com/chenyuAndroid/XToast ,直接使用library內(nèi)的XToast即可。歡迎大家的star or fork,筆者也會繼續(xù)完善XToast的。謝謝大家的閱讀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 47,156評論 22 665
  • 一、系統(tǒng)自帶Toast的源碼分析 1. Toast的調(diào)用顯示 學(xué)過Android的人都知道,彈出一個系統(tǒng)API吐司...
    笑說余生閱讀 5,909評論 8 46
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,029評論 25 709
  • 2017年6月7日 今天高考第一天,朋友圈里都是祝福學(xué)子的話語,高考對于我們那一代人來說是痛苦的,可是經(jīng)歷過...
    雙魚兒0313閱讀 58評論 0 0
  • 這是一件發(fā)生在高鐵上的趣事。 寫這件趣事的原因是因為主人公很帥。 這天,我坐高鐵回家。高鐵站人很多。 我早到了一個...
    justandyou閱讀 468評論 0 1

友情鏈接更多精彩內(nèi)容