前言
用過系統(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:

這是一個縮放效果的Toast:

原理簡述
其實實現(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的。謝謝大家的閱讀。