Toast的高级自定义方式-循序渐进带你了解toast

Posted Mario_oo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Toast的高级自定义方式-循序渐进带你了解toast相关的知识,希望对你有一定的参考价值。

写在前面

对于Toast的使用,相信只要是使用过android的童鞋都不会陌生,它是不需要和用户进行交互的一个提示框。接下来,让我们一步步自定义Toast,全方位的玩转Toast,实现它的不同显示需求。从此再也不怕提示的各种变态需求。~
先来看看效果图,苦逼的华为手机,4.4版本,没root,只能连上电脑,再通过录制电脑屏幕上的手机画面录屏,求推荐好方法录屏。~

1.最基本的Toast

Toast.makeText(getApplicationContext(),"最基本的Toast",Toast.LENGTH_SHORT).show();

这个不用多讲。

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的3个构造函数
   ......
   //b.初始化画笔的参数和矩形参数
   .....



a.初始化其中的一些变量,实现3个构造函数。

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 CustomToastView(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();

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;
    

接着在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();

ok~该讲的已经讲完了,不该讲的也不知道讲啥了,以后有机会再详细讲解下Toast的源码。
上完整的代码:csdn:

以上是关于Toast的高级自定义方式-循序渐进带你了解toast的主要内容,如果未能解决你的问题,请参考以下文章

Android 高级自定义Toast及源码解析

安卓开发问题,页面上只有一个按钮,我要点击按钮显示一个toast,但是点击后没有出现!我在toas

用jq实现toas 效果

android开发中关于toast的使用

46道面试题带你了解高级Java面试,在线面试指南

一篇文带你从0到1了解建站及完成CMS系统编写