android 反射实现自定义toast 自由设置消失时间

先上一张图,再说话:

在android中,Toast是用来实现简要信息展示,与界面无关的一种无可点击操作的悬浮层,它和PopupWindow还有dialog不同,popupWindow和dialog是基于activity,detcorView来展示的,它们显示的时候是不可以操作底层view的,所以对于一些提示类的需求,同时还要操作activity的时候最好还是用Toast来完成。

现在举一个列子,一个需求:自定义一个toast,固定位置,而且消失时间需要自定义,该怎么实现呢,Toast有个方法setDuration().但是由于使用了注解,这个方法只支持Toast.LENGTH_LONG和Toast.LENGTH_SHORT 2个同样添加了注解的参数,所以我们无法自定义toast显示的具体时间,当然一般的toast肯定是在8s以内,超过了8s,就可以跟产品说做弹窗了,这个时候就用dialog和popupwindow了,上面是问题,下面给出解决方案以及方案中存在的问题:

基本原理是用到反射来调用show和hide方法,Toast原代码中有一个对象TN,toast的实际展示都是由它来完成的,TN使用了AIDI实现与底层native的交互,使用binder与WindowManager进行通信,最后由WindowManager将toast显示在窗口,下面就通过反射拿到TN对象进行操作.

   

package com.douyu.module.wheellottery.util;

import android.content.Context;
import android.os.Handler;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import android.widget.Toast;

import com.douyu.module.wheellottery.R;

import java.lang.reflect.Field;
import java.lang.reflect.Method;


/**
 * 作者: chengwenchi
 * 创建于: 2017/7/27
 */

public class WLToast {
    /**
     * 获取当前Android系统版本
     */
    static int currentapiVersion = android.os.Build.VERSION.SDK_INT;
    /**
     * 离场动画持续时间
     */
    private static final int TIME_END_ANIM = 5000;
    /**
     * UI线程句柄
     */
    Handler mHandler;
    /**
     * 内容对象
     */
    Context mContext;

    /**
     * 顶层布局
     */
    LinearLayout mTopView;
    /**
     * 布局属性
     */
    LayoutParams lp_MM;
    /**
     * 反射过程中是否出现异常的标志
     */
    boolean hasReflectException = false;

    /**
     * 单例
     */
    private static WLToast instance;

    private TextView tvContext;

    /**
     * 获得单例
     *
     * @param context
     * @return
     */
    public static WLToast getInstance(Context context) {
        if (instance == null) {
            instance = new WLToast(context);
        }
        return instance;
    }

    private WLToast(Context context) {
        if (context == null || context.getApplicationContext() == null) {
            throw new NullPointerException("context can't be null");
        }
        mContext = context.getApplicationContext();
        initView();
        initTN();

    }


    /**
     * 初始化视图控件
     */
    public void initView() {
        mHandler = new Handler(mContext.getMainLooper());
        View view = LayoutInflater.from(mContext).inflate(R.layout.wl_toast, null);
        tvContext = (TextView) view.findViewById(R.id.wl_toast_context);
        lp_MM = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mTopView = new LinearLayout(mContext);
        mTopView.setLayoutParams(lp_MM);
        mTopView.setOrientation(LinearLayout.VERTICAL);
        mTopView.setGravity(Gravity.TOP);
        mTopView.addView(view);

    }

    public final void show(String msg) {
        tvContext.setText(msg);
        showToast();
        hide();
    }


    public final void hide() {
        //动画结束后移除控件
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                hideToast();
            }
        }, TIME_END_ANIM);
    }

    /* 以下为反射相关内容 */
    Toast mToast;
    Field mTN;
    Object mObj;
    Method showMethod, hideMethod;

    /**
     * 通过反射获得mTN下的show和hide方法
     */
    private void initTN() {
        mToast = new Toast(mContext);
        mToast.setView(mTopView);
        Class<Toast> clazz = Toast.class;
        try {
            mTN = clazz.getDeclaredField("mTN");
            mTN.setAccessible(true);
            mObj = mTN.get(mToast);
            showMethod = mObj.getClass().getDeclaredMethod("show", new Class<?>[0]);
            hideMethod = mObj.getClass().getDeclaredMethod("hide", new Class<?>[0]);
            hasReflectException = false;
        } catch (NoSuchFieldException e) {
            hasReflectException = true;
            System.out.println(e.getMessage());
        } catch (IllegalAccessException e) {
            hasReflectException = true;
            System.out.println(e.getMessage());
        } catch (IllegalArgumentException e) {
            hasReflectException = true;
            System.out.println(e.getMessage());
        } catch (NoSuchMethodException e) {
            hasReflectException = true;
            System.out.println(e.getMessage());
        }
    }

    /**
     * 通过反射获得的show方法显示指定View
     */
    private void showToast() {
        try {
            //高版本需要再次手动设置mNextView属性,2系列版本不需要
            if (currentapiVersion > 10) {
                Field mNextView = mObj.getClass().getDeclaredField("mNextView");
                mNextView.setAccessible(true);
                mNextView.set(mObj, mTopView);
            }
            if (showMethod != null) {
                showMethod.invoke(mObj, new Object[0]);
            } else {
                mToast.setDuration(Toast.LENGTH_LONG);
                mToast.show();
            }

            hasReflectException = false;
        } catch (Exception e) {
            hasReflectException = true;
            System.out.println(e.getMessage());
        }
    }

    /**
     * 通过反射获得的hide方法隐藏指定View
     */
    public void hideToast() {
        try {
            if (mToast != null) {
                mToast.cancel();
            }
            hasReflectException = false;
        } catch (Exception e) {
            hasReflectException = true;
            System.out.println(e.getMessage());
        }
    }

    public void removeAll() {
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
    }


    /**
     * 是否是三星手机
     *
     * @return
     */
    private boolean isSamSung() {
        String brand = android.os.Build.BRAND;
        if (!TextUtils.isEmpty(brand)) {
            if (brand.equalsIgnoreCase("samsung")) {//适配三星手机且版本为7.0以上的系统
                return true;
            }
        }
        return false;
    }
}

 利用TN对象调用show方法是不会自动隐藏Toast的,这时,我们手动调用TN的hide方法,就可以进行延时操作了,然后有一个需要注意的点,利用LayoutInflater自定义的view需要一个容器,所以添加了一个container来显示自己加载的xml文件,并控制其位置。

PS:在红米和小米手机上利用反射机制,无法获取到show和hide 两个Method方法,这里暂时不知道具体原因,也许是OEM自定义改变了底层的一些架构对其影响,只是猜测。

猜你喜欢

转载自blog.csdn.net/weixin_42607020/article/details/81121983
今日推荐