写在前面 对于Toast相信只要用过,android童鞋并不陌生,它是一个不需要与用户互动的提示框。接下来,让我们一步一步地定制它Toast,全方位玩Toast,实现其不同的显示需求。从那时起,我们不再害怕提示的各种异常需求。~ 先看效果图,华为手机,4.4版本,没root,只能连上电脑,再通过录制电脑屏幕上的手机画面录屏,求推荐好方法录屏。~
1.最基本的Toast Toast.makeText(getApplicationContext(),"最基本的Toast",Toast.LENGTH_SHORT).show(); 1 不用多说。
2.定制位置Toast 通过Toast设置类自带定义位置的方法toast位置。
Toast toast=Toast.makeText(getApplicationContext(),"定制位置Toast",Toast.LENGTH_SHORT); /** *Toast.setGravity(gravity,xOffset,yOffset); *@gravity:toast的位置 *@xOffset:相对于gravity x方向偏移。yOffset:相对于gravity y方向偏移。 */ toast.setGravity(Gravity.LEFT,50,0); toast.show(); 3.带图片的toast 我讲了前两个最基本的。Toast,现在先来看看toast.markText看看源码toast它是如何显示的。 源码位置:frameworks/base/core/java/Android/widght/Toast.java (Toast#makeText()) 要增加的view布局如下:frameworks/base/core/res/res/layout/transient_notification.xml。里面只有一个TextView没什么好说的。
public static Toast makeText(Context context, CharSequence text, int duration) { Toast result = new Toast(context); LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); result.mNextView = v; result.mDuration = duration; return result; } 从上面的源码中可以得到什么:1.new一个Toast类,获得当前的context;2.LayoutInflater,动态载入layout的类,将view布局载入。3.获得view中的textview中文信息。4.设置toast的view和duration属性。5.返回toast.从而实现了toast的markText方法。 ok!,搞懂了toast.markText我们可以在这部分设置自定义源代码view的toast. 1.创建一个custom_toast.xml,一个LinearLayout,水平方向,ImageView TextView。设置它们的各种属性。(代码很简单,所以不。 2.自定义Toast.直接上代码
Toast customToast = new Toast(MainActivity.this.getApplicationContext()); //获得view的布局 View customView = LayoutInflater.from(MainActivity.this).inflate(R.layout.custom_toast,null); ImageView img = (ImageView) customView.findViewById(R.id.iv); TextView tv = (TextView) customView.findViewById(R.id.tv); //设置ImageView的图片 img.setBackgroundResource(R.drawable.ab); //设置textView中的文字 tv.setText("我是带图片的自定义位置toast"); //设置toast的View,Duration,Gravity最后显示 customToast.setView(customView); customToast.setDuration(Toast.LENGTH_SHORT); customToast.setGravity(Gravity.CENTER,0,0); customToast.show(); 4.自定义View超高级带动画Toast. 其实这是3。toast加强版。用我们定制的图片代替里面的图片。view,通过自定义view,实现多种多样Toast. 1.创建自定义view.CustomToastView继承View. 整体的Custom结构(具体实现代码如下):
public class CustomToastView extends View { //a.一些初始化的变量。 ... //a.实现CustomToastView三个结构函数 ... //b.初始化画笔的参数和矩形参数 ...
} a.部分变量初始化,实现三个结构函数。
public class CustomToastView extends View { //矩形,设置toast布局时用 RectF rectF =new RectF(); ///属性动画 ValueAnimator valueAnimator; float mAnimatedValue = 0f; //自定义view的画笔 private Paint mPaint;
private float mWidth = 0f; //view的宽 private float mEyeWidth = 0f; ///笑脸的眼睛半径 private float mPadding = 0f; //view的偏移量。 private float endAngle = 0f; ///圆弧结束数
//左眼或右眼 private boolean isSmileLeft = false; private boolean isSmileRight = false;
public CustomToastViw(Context context) { super(context); } public CustomToastView(Context context, AttributeSet attrs) { super(context, attrs); } public CustomToastView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } ....... } b.设置画笔的参数以及矩形的参数。
private void initPaint() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.parseColor("#5cb85c")); mPaint.setStrokeWidth(dip2px(2)); } private void initRect() { rectF = new RectF(mPadding, mPadding, mWidth - mPadding, mWidth - mPadding); } //dip转px。为了支持多分辨率手机 public int dip2px(float dpValue) { final float scale = getContext().getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } c.重写onMeasure
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); initPaint(); initRect(); mWidth = getMeasuredWidth(); //当前view在父布局里的宽度。即view所占宽度。 mPadding = dip2px(10); mEyeWidth = dip2px(3); } d.重写OnDraw
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setStyle(Paint.Style.STROKE); //画微笑弧(从左向右画弧) canvas.drawArc(rectF, 180, endAngle, false, mPaint); //设置画笔为实心 mPaint.setStyle(Paint.Style.FILL); //左眼 if (isSmileLeft) { canvas.drawCircle(mPadding + mEyeWidth + mEyeWidth / 2, mWidth / 3, mEyeWidth, mPaint); } //右眼 if (isSmileRight) { canvas.drawCircle(mWidth - mPadding - mEyeWidth - mEyeWidth / 2, mWidth / 3, mEyeWidth, mPaint); } } e.自定义View中的动画效果实现
/** * startAnim()不带参数的方法 */ public void startAnim() { stopAnim(); startViewAnim(0f, 1f, 2000); }
/** * 停止动画的方法 * */ public void stopAnim() { if (valueAnimator != null) { clearAnimation(); isSmileLeft = false; isSmileRight = false; mAnimatedValue = 0f; valueAnimator.end(); } } /** * 开始动画的方法 * @param startF 起始值 * @param endF 结束值 * @param time 动画的时间 * @return */ private ValueAnimator startViewAnim(float startF, final float endF, long time) { //设置valueAnimator 的起始值和结束值。 valueAnimator = ValueAnimator.ofFloat(startF, endF); //设置动画时间 valueAnimator.setDuration(time); //设置补间器。控制动画的变化速率 valueAnimator.setInterpolator(new LinearInterpolator()); //设置监听器。监听动画值的变化,做出相应方式。 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatedValue = (float) valueAnimator.getAnimatedValue(); //如果value的值小于0.5 if (mAnimatedValue < 0.5) { isSmileLeft = false; isSmileRight = false; endAngle = -360 * (mAnimatedValue); //如果value的值在0.55和0.7之间 } else if (mAnimatedValue > 0.55 && mAnimatedValue < 0.7) { endAngle = -180; isSmileLeft = true; isSmileRight = false; //其他 } else { endAngle = -180; isSmileLeft = true; isSmileRight = true; } //重绘 postInvalidate(); } }); if (!valueAnimator.isRunning()) { valueAnimator.start(); } return valueAnimator; } 好了,就这么多,自定义view大功告成。
2.toast要用的view的xml。没啥多说的直接上代码。有三个xml. 一个是backgroud_toast.xml(设置view的样式。)
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#FFFFFF"></solid> <stroke android:color="#C4CDE0"></stroke> <corners android:radius="10dp"></corners>
</shape> 一个text_toast.xml(设置textView的样式)
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#5cb85c"></solid> <stroke android:color="#C4CDE0"></stroke> <corners android:bottomRightRadius="10dp" android:topRightRadius="10dp"></corners>
</shape> 一个是smile_toast.xml(显示的toast的view布局方式)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#00000000" android:orientation="vertical"> <LinearLayout android:id="@+id/base_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="25dp" android:layout_marginLeft="30dp" android:layout_marginRight="30dp" android:layout_marginTop="25dp" android:background="@drawable/backgroud_toast" android:orientation="horizontal"> <LinearLayout android:id="@+id/linearLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <com.example.yyh.toasttest.CustomToastView android:id="@+id/successView" android:layout_width="50dp" android:layout_height="50dp" android:layout_gravity="center_vertical|left" android:layout_margin="10px" android:gravity="center_vertical|left" /> </LinearLayout> <TextView android:id="@+id/toastMessage" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:padding="10dp" android:text="New Text" /> </LinearLayout> </LinearLayout> 3.接下来就是在代码中将自定义的toast,加入进去。大功告成。
//在MainActivity中声明CustomToastView static CustomToastView customToastView;
Toast smileToast=new Toast(MainActivity.this.getApplicationContext()); //view布局 View smileView =LayoutInflater.from(MainActivity.this.getApplicationContext()).inflate(R.layout.smile_toast,null,false); TextView text = (TextView) smileView.findViewById(R.id.toastMessage); text.setText("我是带动画的toast"); //给customToastView增加动画效果 customToastView=(CustomToastView)smileView.findViewById(R.id.smileView); customToastView.startAnim(); //设置text的背景样式 text.setBackgroundResource(R.drawable.text_toast); text.setTextColor(Color.parseColor("#FFFFFF")); smileToast.setView(smileView); smileToast.setDuration(Toast.LENGTH_SHORT); smileToast.show(); 是不是脑光一亮~,笑脸的toast只是个启发,有了这个思路我们就可以设置不同的效果显示方式。
5.带出入效果的Toast 大家如果用的小米手机,就会发现,小米手机弹出的toast,有一个从底部上移弹出的效果。这个效果也是比较特别的。我们就来试试也实现下这个效果。 我们将4.自定义View带动画超高级的Toast.进行进一步的扩展,利用悬浮窗的原理,完成从底部弹出toast的效果。(其实查看源码 Toast实质上就是用到了悬浮窗的知识WindowManager.addView;和 mWM.removeView(mView);)来实现Toast的显示和消失的。当然,这里我们不再剖析源码,大家知道就行了 对悬浮窗的知识不是很了解的童鞋,可以去看我的上一篇文章: 仿360加速球。(实现内存释放) 首先,新建一个MiUiToast类。 1.一些需要的变量。这里用到了上面的自定义的view.
//窗口管理类,用来管理Toast的显示和隐藏。 private WindowManager mWdm; //自定义的view. private CustomToastView mToastView; //toast的参数 private WindowManager.LayoutParams mParams; //是否显示toast. private int showTime; private boolean mIsShow;//记录当前Toast的内容是否已经在显示 //要显示的view. private final View smileView; 2.相关函数的设置。
//显示时间的设置相关 public int getShowTime() { return showTime; } public void setShowTime(int showTime) { this.showTime = showTime; } public static MiUiToast MakeText(Context context, String text, int showTime) { MiUiToast result = new MiUiToast(context, text, showTime); return result; } private MiUiToast(Context context, String text, int time){ setShowTime(time); mIsShow = false;//记录当前Toast的内容是否已经在显示 mWdm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); smileView = LayoutInflater.from(context.getApplicationContext()).inflate(R.layout.smile_toast, null, false); TextView text1 = (TextView) smileView.findViewById(R.id.toastMessage); text1.setText("我是带动画的toast"); mToastView=(CustomToastView) smileView.findViewById(R.id.smileView); mToastView.startAnim(); text1.setBackgroundResource(R.drawable.text_toast); text1.setTextColor(Color.parseColor("#FFFFFF")); //设置布局参数 setParams(); } //toast.布局参数的设置。 private void setParams() { mParams = new WindowManager.LayoutParams(); mParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mParams.format = PixelFormat.TRANSLUCENT; mParams.windowAnimations = R.style.anim_view;//设置进入退出动画效果 mParams.type = WindowManager.LayoutParams.TYPE_TOAST; mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mParams.gravity = Gravity.CENTER_HORIZONTAL; mParams.y = 250; }
//显示Toast. public void show(){ if(!mIsShow){//如果Toast没有显示,则开始加载显示 mIsShow = true; mWdm.addView(smileView, mParams);//将其加载到windowManager上 } }
//取消toast. public void cancel(){ mWdm.removeView(smileView); mIsShow = false; } 3.在ManiActivity中设置MiUiToast,显示。大功告成。
private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what==007){ miUitoast.cancel(); } } }; private MiUiToast miUitoast; if(miUitoast == null){ miUitoast = MiUiToast.MakeText(this, "仿小米Toast", 2000); } miUitoast.show(); handler.sendEmptyMessageDelayed(007,miUitoast.getShowTime()); 6.关于Toast几个不为人知的秘密(敲黑板,认真脸,加分项啊加分项。) 1.toast的显示时间只有两种可能。我们查看源码可以得知它只有2秒和3.5秒。(long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;)
private void scheduleTimeoutLocked(ToastRecord r) { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; mHandler.sendMessageDelayed(m, delay); } private static final int LONG_DELAY = 3500; // 3.5 seconds private static final int SHORT_DELAY = 2000; // 2 seconds 那如果我们要控制toast的显示时间随意该怎么办呢,认真看过的上面的文章的童鞋,相信已经有了思路。没错,自定义view的toast,利用WindowManager+Handler,确定一定时间来remove这个自定义的Toast.就行了。参考5.带出入效果的Toast 当然还有另一种思路,利用反射拿到show方法,我试了好久,都是有错误,最后寻找原因但是好像在andorid4.0往上,这种方法就用了不了。这里就不讲了。。。 还有一种思路。利用Timer+Handler也可以来控制Toast的显示时间问题。那么开始吧~ 新建一个TimeToast类,定义两个Timer,一个是显示Toast,一个是取消Toast.代码不难直接上~
/** * Created by yyh on 2016/10/27. */ public class TimeToast { //定义的显示时间 private double time; private static Handler handler; //显示的计时器 private Timer showTimer; //取消的计时器 private Timer cancelTimer;
private Toast toast;
private TimeToast(){ showTimer = new Timer(); cancelTimer = new Timer(); } public void setTime(double time) { this.time = time; } public void setToast(Toast toast){ this.toast = toast; }
public static TimeToast makeText(Context context, String text, double time){ TimeToast toast1= new TimeToast(); toast1.setTime(time); toast1.setToast(Toast.makeText(context, text, Toast.LENGTH_SHORT)); handler = new Handler(context.getMainLooper()); return toast1; } public void show(){ toast.show(); if(time > 2){ showTimer.schedule(new TimerTask() { @Override public void run() { handler.post(new ShowRunnable()); } }, 0, 1900); } cancelTimer.schedule(new TimerTask() { @Override public void run() { handler.post(new CancelRunnable()); } }, (long)(time * 1000)); } private class CancelRunnable implements Runnable{ @Override public void run() { showTimer.cancel(); toast.cancel(); } } private class ShowRunnable implements Runnable{ @Override public void run() { toast.show(); } } } 之后在ManiActivity中调用这个类就行了
TimeToast timeToast=TimeToast.makeText(getApplicationContext(),"显示时间自定的Toast",6); timeToast.show(); 1 2 2.Toast显示的问题,当我们连续点击Toast的时候,居然一直在显示,点击30多下,结果这条Toast显示了将近2分钟。这样用户的体验很不好。(原因是因为Toast的管理是在队列中,点击一次,就会产生一个新的Toast,所以要等这个队列中的Toast处理完,这个显示Toast的任务才算结束。) so~ 思路来了,我们可以把Toast改成单例模式,没有Toast再新建它,这样也就解决了连续点击Toast,一直在显示的问题。~ 先上新建的单例模式的SingleToast类:
public class SingleToast { private static Toast mToast; /**双重锁定,使用同一个Toast实例*/ public static Toast getInstance(Context context){ if (mToast == null){ synchronized (SingleToast.class){ if (mToast == null){ mToast = new Toast(context); } } } return mToast; } } 1 接着在ManiActivity中进行应用,就是这么随意~ 大功告成~
Toast singleToast=SingleToast.getInstance(getApplicationContext()); View singleCustomView = LayoutInflater.from(MainActivity.this).inflate(R.layout.custom_toast,null); ImageView img1 = (ImageView) singleCustomView.findViewById(R.id.iv); TextView tv1 = (TextView) singleCustomView.findViewById(R.id.tv); img1.setBackgroundResource(R.drawable.ic_launcher); tv1.setText("这是第"+num+++"遍 点击我了~~"); singleToast.setView(singleCustomView); singleToast.setDuration(Toast.LENGTH_SHORT); singleToast.show();