Android开发经验谈Android进阶之路移动开发进阶

【Toast】关于Android Toast 的一丁点认识

2018-11-20  本文已影响13人  Jboob
图片来自百度图片,如有侵权,请联系作者删除

  Android Toast 在项目中是普遍应用的一个控件,应该也是一个使用非常简单的控件。这里,有人会问,Toast 这么简单干嘛还要花时间去学习呢,项目里直接 Toast.makeText 就可以了啊。我的回答是:“因为简单,所以需要去学习,去了解”。

为什么我还要去做一个自定义Toast?

Android Toast 如果在某个场景交互中,一不小心多次触发了 Toast show 结果是不停地在你屏幕显示同样的 Toast 信息。我是个有时间洁癖的人,不喜欢因为你多显示一次 Toast 信息浪费我一秒钟或两秒钟,在碎片时间学习中,我更希望花一秒钟看到我更喜欢看到的知识,集中精力去得到我想要的东西。
废话少扯,进入主题。
先看看Android 系统 Toast 的实现源码:
Toast MakeText:

    /**
     * Make a standard toast that just contains a text view.
     *
     * @param context  The context to use.  Usually your {@link android.app.Application}
     *                 or {@link android.app.Activity} object.
     * @param text     The text to show.  Can be formatted text.
     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
     *                 {@link #LENGTH_LONG}
     *
     */
    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }
    /**
     * Make a standard toast to display using the specified looper.
     * If looper is null, Looper.myLooper() is used.
     * @hide
     */
    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);

        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;
    }

再看看Toast 布局:

  <?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/layout/transient_notification.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">

    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/bright_foreground_dark"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"
        />

</LinearLayout>

Toast show 和 cancel 方法:

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
   /**
     * 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 cancel() {
        mTN.cancel();
    }

  通过简单查看Toast 源码得知一些信息

  1. Toast 实现没有对同一条消息显示进行过滤,show 方法调用通知服务 enqueueToast 显示信息。
  2. Toast 布局很简单,就一个LinearLayout 的圆角布局和显示信息的阴影效果。
android:background="?android:attr/toastFrameBackground"
 android:shadowColor="#BB000000"
 android:shadowRadius="2.75"

显然,系统的Toast 不满足我的需求,我需要一个自定义一个能够过滤相同信息的 Toast 。

自定义Toast 主要思想:

  1. 创建对应构造器。采用设计模式 Builder 模式构造一个需要的Toast。主要针对有个性化 Toast 需求提供,如自定义背景颜色,自定义字体颜色,自定义 Toast 背景布局等。
  2. 在Toast 上一次显示后没有 cancel 前,界面某个位置多次触发,只显示同样的Toast 信息一次。取显示信息对应的 hashCode作为key,缓存对应的Toast 信息,如果我每次传入的信息都是同样的则不做显示,Handler 控制对应的显示时间,即使取消 Toast 显示,清除上次Toast 显示信息。
  3. Toast 背景圆角问题的一些思考。Android Toast 圆角布局背景调用的Android 系统 xml 布局,如果动态修改 View 布局颜色会丢失 圆角。当然,采用简单的自定义一个圆角布局就可以解决问题的,具体圆角布局代码可参照代码里 RoundLinearLayout.java,具体使用方式太简单了,这里不做细说。

自定义相关代码展示:

private void initCustomToast(CustomToastBuilder builder) {
        runnable = RUNNABLES.get(text.hashCode());
        if (isNull(runnable)) {
            customToast = new CustomToast(mContext);
            runnable = new ToastRunnable(customToast);
            RUNNABLES.put(text.hashCode(), runnable);
            long showTime = builder.showTime;
            if (showTime > DEFAULT_SHOW_TIME){
                mHander.postDelayed(runnable, showTime);
            }else {
                mHander.postDelayed(runnable, DEFAULT_SHOW_TIME);
            }
            customToast.show(builder.customToastCreator,builder.text,builder.textResId);
        }
    }
 public void show(CharSequence text) {
        if (!text.equals(TEXTS.get(text.hashCode()))) {
            TEXTS.put(text.hashCode(), text);
            systemToast = new SystemToast(mContext);
            runnable = new ToastRunnable(systemToast);
            mHander.postDelayed(runnable, DEFAULT_SHOW_TIME);
            systemToast.show(text);
        }
    }
private class ToastRunnable implements Runnable {

        private BaseToast toast;

        public ToastRunnable(BaseToast toast) {
            this.toast = toast;
        }

        @Override
        public void run() {
            toast.cancel();
            TEXTS.remove(toast.getText().hashCode());
            RUNNABLES.remove(toast.getText().hashCode());
            toast = null;
            runnable = null;
        }
    }

圆角布局:

 @SuppressLint("WrongConstant")
    private void drawRoundDrawable() {
        Log.d(TAG, "drawRoundDrawable: "+cornesRadius);
        if (null == gradientDrawable) {
            return;
        }
        if (cornesRadius != 0) {
            gradientDrawable.setCornerRadius(cornesRadius);
            gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);
        } else if (null != radii) {
            gradientDrawable.setCornerRadii(radii);
            gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);
        }
        setBackgroundDrawable();
    }

自定义 Toast 如何使用,代码(Kotlin)如下:

1. 最简单的使用

JToast.getInstance(this).show("show 一个!") 

2. 构造器的使用

var creator = SystemToastCreator.build()
                    .shadowColor(Color.parseColor("#2F4F4F"))
                    .shadowRadius(15f)
                    .setTextColor(Color.parseColor("#ffffff"))
                    .creator()
 JToast.build()
                    .systemToastBuilder()
                    .setToastCreator(creator)
                    .setShowTime(2000)
                    .setText("show creator!")
                    .show(this)
var customCreator = CustomToastCreator.build()
                    .setCustomView(R.layout.layout_toast)
                    .setTextColor(Color.WHITE)
                    .setBackgroundRound(true)
                    .creator()
JToast.build()
                    .customToastBuilder()
                    .setToastCreator(customCreator)
                    .setShowTime(2000)
                    .setText("show customCreator!",R.id.message)
                    .show(this)

我自定义的代码都太简单了,这里就不太啰嗦了。不懂的也随时可以联系我。

  总结: 我总感觉我的实现方式不太友好,并不完善,欢迎各位评论区提出你们宝贵的建议,或直接GitHub 把你们的想法提交。非常感谢各位大神的批评指正!

最后给出 GitHub 链接: JToast

上一篇下一篇

猜你喜欢

热点阅读