Toast拓展--自定义显示时间和动画
Toast拓展–自定義顯示時間和動畫
我們在Android應用開發中經常會需要在界面上彈出一個對界面操作無影響的小提示框來提示用戶一些信息,這時候一般都會使用Android原生的Toast類
Toast.makeText(mContext, "消息內容", Toast.LENGTH_SHORT).show();一開始覺得,挺好用的,就有點什么消息都用Toast顯示了。
但是用久了就發現,Toast的顯示和消失動畫不符合自己的要求,顯示時間也只有SHORT和LONG兩種選擇,好像不太夠用。
于是,在閱讀了Toast的源碼后對Toast進行了拓展,原生Toast包含了以下方法給用戶修改顯示內容:
setView(View):void setDuration(int):void setMargin(float,float):void setGravity(int,int,int):void setText(int):void setText(CharSequence):void分別是直接替換視圖、設置顯示時長、設置邊距屬性、設置顯示位置、設置顯示文字內容。
基于原有的Toast上對其進行拓展,修改及增加以下兩個方法:
setDuration(int):void setAnimations(int):void設置顯示時長方法拓展為可以自定義顯示時間,參數單位秒,提供三個默認值:LENGTH_SHORT,LENGTH_LONG,LENGTH_ALWAYS,分別對應原生Toast的LENGTH_SHORT,LENGTH_LONG,以及總是顯示。要注意的是總是顯示需要在合適的時候自己調用hide()方法隱藏,否則會影響其他窗口的正常顯示。
下圖是使用自定義動畫和自定義顯示時間的Toast示例
廢話不多說,先上工具類源碼跟example:
ExToast.java
import android.content.Context; import android.content.res.Resources; import android.os.Handler; import android.view.View; import android.view.WindowManager; import android.widget.Toast;import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;/*** Created by kj on 16-06-32.*/ public class ExToast {private static final String TAG = "ExToast";public static final int LENGTH_ALWAYS = 0;public static final int LENGTH_SHORT = 2;public static final int LENGTH_LONG = 4;private Toast toast;private Context mContext;private int mDuration = LENGTH_SHORT;private int animations = -1;private boolean isShow = false;private Object mTN;private Method show;private Method hide;private Handler handler = new Handler();public ExToast(Context context){this.mContext = context;if (toast == null) {toast = new Toast(mContext);}}private Runnable hideRunnable = new Runnable() {@Overridepublic void run() {hide();}};/*** Show the view for the specified duration.*/public void show(){if (isShow) return;initTN();try {show.invoke(mTN);} catch (InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}isShow = true;//判斷duration,如果大于#LENGTH_ALWAYS 則設置消失時間if (mDuration > LENGTH_ALWAYS) {handler.postDelayed(hideRunnable, mDuration * 1000);}}/*** Close the view if it's showing, or don't show it if it isn't showing yet.* You do not normally have to call this. Normally view will disappear on its own* after the appropriate duration.*/public void hide(){if(!isShow) return;try {hide.invoke(mTN);} catch (InvocationTargetException | IllegalAccessException e) {e.printStackTrace();}isShow = false;}public void setView(View view) {toast.setView(view);}public View getView() {return toast.getView();}/*** Set how long to show the view for.* @see #LENGTH_SHORT* @see #LENGTH_LONG* @see #LENGTH_ALWAYS*/public void setDuration(int duration) {mDuration = duration;}public int getDuration() {return mDuration;}public void setMargin(float horizontalMargin, float verticalMargin) {toast.setMargin(horizontalMargin,verticalMargin);}public float getHorizontalMargin() {return toast.getHorizontalMargin();}public float getVerticalMargin() {return toast.getVerticalMargin();}public void setGravity(int gravity, int xOffset, int yOffset) {toast.setGravity(gravity,xOffset,yOffset);}public int getGravity() {return toast.getGravity();}public int getXOffset() {return toast.getXOffset();}public int getYOffset() {return toast.getYOffset();}public static ExToast makeText(Context context, CharSequence text, int duration) {Toast toast = Toast.makeText(context,text,Toast.LENGTH_SHORT);ExToast exToast = new ExToast(context);exToast.toast = toast;exToast.mDuration = duration;return exToast;}public static ExToast makeText(Context context, int resId, int duration)throws Resources.NotFoundException {return makeText(context, context.getResources().getText(resId), duration);}public void setText(int resId) {setText(mContext.getText(resId));}public void setText(CharSequence s) {toast.setText(s);}public int getAnimations() {return animations;}public void setAnimations(int animations) {this.animations = animations;}private void initTN() {try {Field tnField = toast.getClass().getDeclaredField("mTN");tnField.setAccessible(true);mTN = tnField.get(toast);show = mTN.getClass().getMethod("show");hide = mTN.getClass().getMethod("hide");/**設置動畫*/if (animations != -1) {Field tnParamsField = mTN.getClass().getDeclaredField("mParams");tnParamsField.setAccessible(true);WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);params.windowAnimations = animations;}/**調用tn.show()之前一定要先設置mNextView*/Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");tnNextViewField.setAccessible(true);tnNextViewField.set(mTN, toast.getView());} catch (Exception e) {e.printStackTrace();}}}ExToast example
ExToast exToast = ExToast.makeText(context,"message",ExToast.LENGTH_ALWAYS); exToast.setAnimations(R.style.anim_view); exToast.show(); //使用LENGTH_ALWAYS注意在合適的時候調用hide() exToast.hide(); //顯示5秒的Toast ExToast exToast = ExToast.makeText(context,"message",5); exToast.show();上面的代碼可以實現自定義xml窗口動畫,以及長時間顯示Toast的功能。
下面看一下R.style.anim_view的內容,窗口動畫可以通過@android:windowEnterAnimation和@android:windowExitAnimation定義窗口進場及退場效果
style.xml(放置在res/values/style.xml文件)
<style name="anim_view"><item name="@android:windowEnterAnimation">@anim/anim_in</item><item name="@android:windowExitAnimation">@anim/anim_out</item> </style>anim_in.xml(放置在res/anim目錄下)
<set xmlns:android="http://schemas.android.com/apk/res/android"><translate android:fromXDelta="0"android:fromYDelta="0"android:toXDelta="0"android:toYDelta="85"android:duration="1"/><translate android:fromXDelta="0"android:fromYDelta="0"android:toXDelta="0"android:toYDelta="-105"android:duration="350"android:fillAfter="true"android:interpolator="@android:anim/decelerate_interpolator"/><alpha android:fromAlpha="0"android:toAlpha="1"android:duration="100"/><translate android:fromXDelta="0"android:fromYDelta="0"android:toXDelta="0"android:toYDelta="20"android:duration="80"android:fillAfter="true"android:startOffset="350"/> </set>anim_out.xml(放置在res/anim目錄下)
<set xmlns:android="http://schemas.android.com/apk/res/android"><alpha android:fromAlpha="1"android:toAlpha="0"android:duration="800"/> </set>以上動畫是模仿小米Toast彈出動畫的示例,具體動畫可以根據個人喜好自定義。
拓展Toast的工具類及使用方式已經介紹完畢,下面的內容是對于該工具類的設計原理解析,不趕時間并且有興趣的同學可以繼續往下看。
ExToast原理解析
剛才講到,Toast的使用,有很多限制,其中包括系統原生的Toast是呈隊列顯示出來的,必須要等到前一條Toast消失才會顯示下一條。
相信很多同學都遇到過這個問題,比如我做一個按鈕,點擊的時候顯示一個toast,然后做了個小小的壓力測試:狂按保存按鈕!于是toast隊列排了好長一條,一直在顯示,等到一兩分鐘才結束。
通過閱讀Toast源碼,可以看到里面的Toast.show()和Toast.cancel()方法:
public void show() {if (mNextView == null) {throw new RuntimeException("setView must have been called");}INotificationManager service = getService();String pkg = mContext.getPackageName();TN tn = mTN;tn.mNextView = mNextView;try {service.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty} }public void cancel() {mTN.hide();try {getService().cancelToast(mContext.getPackageName(), mTN);} catch (RemoteException e) {// Empty} }可以看到Toast的核心顯示和隱藏是封裝在INotificationManager的enqueueToast方法中,看到enqueue這個詞就知道這是一個隊列處理的函數,它的參數分別是packageName,tn對象,持續時間。結合Toast的顯示效果我們可以猜測這個方法內部實現是隊列顯示和隱藏每一個傳入的Toast。packageName和持續時間我們都很清楚是什么,剩下的重點就在這個tn對象上了。那tn對象到底是什么?
繼續閱讀Toast源碼,可以知道Toast其實是系統虛浮窗的一種具體表現形式,它的核心在于它的一個私有靜態內部類class TN,它處理了Toast的顯示以及隱藏。所以,我們可以通過反射獲取這個TN對象,主動處理Toast的顯示和隱藏,而不經過系統Service
TN類源碼:
private static class TN extends ITransientNotification.Stub {final Runnable mShow = new Runnable() {@Overridepublic void run() {handleShow();}};final Runnable mHide = new Runnable() {@Overridepublic void run() {handleHide();// Don't do this in handleHide() because it is also invoked by handleShow()mNextView = null;}};...final Handler mHandler = new Handler();...View mView;View mNextView;WindowManager mWM;TN() {final WindowManager.LayoutParams params = mParams;params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.format = PixelFormat.TRANSLUCENT;params.windowAnimations = com.android.internal.R.style.Animation_Toast;params.type = WindowManager.LayoutParams.TYPE_TOAST;params.setTitle("Toast");params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;}/*** schedule handleShow into the right thread*/@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}/*** schedule handleHide into the right thread*/@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);}public void handleShow() {...if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();if (context == null) {context = mView.getContext();}mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);...if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeView(mView);}...mWM.addView(mView, mParams);...}}private void trySendAccessibilityEvent() {...}public void handleHide() {...if (mView != null) {// note: checking parent() just to make sure the view has// been added... i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {...mWM.removeView(mView);}mView = null;}} }好吧,上面的代碼太長不想看,那就把核心的代碼挑出來
public void show(){...WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);mWN.addView(mView, mParams); }public void hide(){...WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);mWN.removeView(mView); }核心代碼可以明顯看出,Toast的機制就是往WindowManager添加以及移除view,那只要獲得TN對象,重新封裝一次show()和hide()方法就可以實現自定義顯示時間。
private void initTN() {try {Field tnField = toast.getClass().getDeclaredField("mTN");tnField.setAccessible(true);mTN = (ITransientNotification) tnField.get(toast);/**調用tn.show()之前一定要先設置mNextView*/Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");tnNextViewField.setAccessible(true);tnNextViewField.set(mTN, toast.getView());} catch (Exception e) {e.printStackTrace();} }public show(){initTN();mTN.show(); }代碼中mTN就是從Toast中利用反射獲取的對象,類型是ITransientNotification,這是從android源碼中拿出來的aidl接口,匹配TN的類型。主動調用mTN.show()方法后就會神奇的發現,Toast長時間存在屏幕中,即使離開了app它依然存在,直到調用mTN.hide()后才消失。
Toast顯示時間拓展的問題已經解決了,剩下一個自定義動畫的問題。現在回過頭再看TN類的初始化方法代碼,里面初始化了一個WindowManager.LayoutParams對象,做過懸浮窗功能的同學應該都接觸過它,下面這一句代碼就是定義窗口動畫的關鍵,如果能修改params.windowAnimations就能夠修改窗口動畫。
params.windowAnimations = com.android.internal.R.style.Animation_Toast;很不幸的是,params并不是一個公有的屬性,那就暴力點繼續用反射獲取并且修改窗口動畫
private void initTN() {try {Field tnField = toast.getClass().getDeclaredField("mTN");tnField.setAccessible(true);mTN = (ITransientNotification) tnField.get(toast);/**調用tn.show()之前一定要先設置mNextView*/Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");tnNextViewField.setAccessible(true);tnNextViewField.set(mTN, toast.getView());/**獲取params后重新定義窗口動畫*/Field tnParamsField = mTN.getClass().getDeclaredField("mParams");tnParamsField.setAccessible(true);WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);params.windowAnimations = R.style.anim_view;} catch (Exception e) {e.printStackTrace();} }至此,ExToast的工作原理已經基本解釋完畢。對于本篇反復講到的利用Java反射獲取類里面的私有屬性以及方法,是一個很實用的技能,本篇不詳細解釋Java反射知識,如果不熟悉的同學可以自行查找Java反射相關資料了解。了解完后應該會對ExToast工具類的設計原理很清楚。
對于Toast的更多應用,請期待下一篇文章。轉載請注明出處,謝謝!
總結
以上是生活随笔為你收集整理的Toast拓展--自定义显示时间和动画的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中间件-RabbitMQ学习笔记
- 下一篇: 有关2pc, 3pc,Tcc 的理解