Android通过Window获取View&模拟点击&自定义Toast

本文介绍了一种在Android应用中模拟按钮点击的方法,并展示了如何自定义Toast使其不会抢走Activity或Dialog的焦点。通过反射机制,实现了跨界面的交互控制。

自己记录:
1. 有时候我们有多个Activity或dialog,B中需要拿到A中的某个View,但是无法通过传值的方式来实现;
2. B中需要模拟A中某个button的点击事件;
2. 自定义Toast,最大的好处是不会抢走Activity或Dialog的焦点。

不多说,上代码:
首先是Activity:

package com.amuro.dialogtopdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                TestDialogA t = new TestDialogA(MainActivity.this);
                t.show();

                TestDialogB testDialogB = new TestDialogB(MainActivity.this);
                testDialogB.show();
            }
        });

        findViewById(R.id.bt2).setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                TestDialogA testDialogA = new TestDialogA(MainActivity.this);
                testDialogA.show();

                TestToast testToast = new TestToast(MainActivity.this);
                testToast.show();
            }
        });
    }


}

我们在这里做一个对比,如果用Dialog覆盖Dialog,下面的dialog是会失去焦点的,而Toast覆盖Dialog就不会。
现在我们要在覆盖在上面的界面上模拟点击下面Dialog上的button,先看底下的DialogA:

package com.amuro.dialogtopdemo;

import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import java.util.Date;

/**
 * Created by Amuro on 16/7/23.
 */
public class TestDialogA extends SDKBaseDialog
{
    public TestDialogA(Context context)
    {
        super(context);

    }

    @Override
    protected void initParams()
    {

    }

    @Override
    protected View onCreateContentView()
    {
        return getContentView();
    }

    @Override
    public void onAttachedToWindow()
    {
        super.onAttachedToWindow();

    }

    private  boolean hasFocus;
    @Override
    public void onWindowFocusChanged(boolean hasFocus)
    {
        super.onWindowFocusChanged(hasFocus);
        this.hasFocus = hasFocus;
        Log.e("amuro", hasFocus + "");
//        ToastUtils.show(getContext(), hasFocus ? "hasFocus" : "loseFocus");
    }

    private View getContentView()
    {
        LinearLayout rootLayout = new LinearLayout(getContext());
        LinearLayout.LayoutParams rootParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);
        rootLayout.setLayoutParams(rootParams);
        rootLayout.setBackgroundColor(Color.GREEN);
        rootLayout.setOrientation(LinearLayout.VERTICAL);

        final Button button = new Button(getContext());
        LinearLayout.LayoutParams buttonParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
        button.setLayoutParams(buttonParams);
        button.setText("Test");
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                ToastUtils.show(getContext(), "Test: " + hasFocus);
            }
        });

        Button buttonMock = new Button(getContext());
        LinearLayout.LayoutParams buttonMockParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
        buttonMock.setLayoutParams(buttonMockParams);
        buttonMock.setText("Mock");
        buttonMock.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {

            }
        });


        rootLayout.addView(button);
        rootLayout.addView(buttonMock);
        return rootLayout;
    }

}

再分别看覆盖在上面的DialogB和Toast

package com.amuro.dialogtopdemo;

import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Created by Amuro on 16/7/23.
 */
public class TestDialogB extends SDKBaseDialog
{
    public TestDialogB(Context context)
    {
        super(context);
    }

    private MockTool mockTool;
    @Override
    protected void initParams()
    {
        mockTool = new MockTool();
        buttonConfirmPay = mockTool.getConfirmPayButton(context);
    }

    private Button buttonConfirmPay;


    @Override
    protected View onCreateContentView()
    {
        LinearLayout rootLayout = new LinearLayout(getContext());
        LinearLayout.LayoutParams rootParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);
        rootLayout.setLayoutParams(rootParams);
        rootLayout.setBackgroundColor(Color.WHITE);
        rootLayout.setOrientation(LinearLayout.VERTICAL);

        TextView textView = new TextView(getContext());
        final LinearLayout.LayoutParams buttonParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
        textView.setLayoutParams(buttonParams);
        textView.setText("请等待。。。。。。");


        Button button = new Button(getContext());
        button.setLayoutParams(
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
        button.setText("button");
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                mockTool.mockClick(buttonConfirmPay);
            }
        });

        rootLayout.addView(textView);
        rootLayout.addView(button);
        return rootLayout;
    }

}
package com.amuro.dialogtopdemo;

import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

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

/**
 * Created by Amuro on 16/7/26.
 */
public class TestToast
{
    private Context context;
    private MockTool mockTool;
    private Button buttonConfirmPay;

    public TestToast(Context context)
    {
        this.context = context;
        mockTool = new MockTool();
        buttonConfirmPay = mockTool.getConfirmPayButton(context);
    }

    private Toast toast;
    private View toastView;
    private Object mTN;

    public void show()
    {
        if(toast == null)
        {
            //  先创建一个Toast对象
            toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);
            //  设置Toast信息提示框显示的位置(在屏幕顶部水平居中显示)
            toast.setGravity(Gravity.CENTER, 0, 0);
        }

        try
        {
            if(mTN == null)
            {

                Field fdToastView = toast.getClass().getDeclaredField("mNextView");
                fdToastView.setAccessible(true);
                toastView = (View) fdToastView.get(toast);
                toastView = getToastView();

                //  从Toast对象中获得mTN变量
                Field field = toast.getClass().getDeclaredField("mTN");
                field.setAccessible(true);
                mTN = field.get(toast);


            }

            Field fdTNView = mTN.getClass().getDeclaredField("mNextView");
            fdTNView.setAccessible(true);
            fdTNView.set(mTN, toastView);

            //  TN对象中获得了show方法
            Method method = mTN.getClass().getDeclaredMethod("show", null);
            //  调用show方法来显示Toast信息提示框
            method.invoke(mTN, null);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private View getToastView()
    {
        LinearLayout rootLayout = new LinearLayout(context);
        LinearLayout.LayoutParams rootParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);
        rootLayout.setLayoutParams(rootParams);
        rootLayout.setBackgroundColor(Color.GREEN);
        rootLayout.setOrientation(LinearLayout.VERTICAL);

        final TextView textView = new TextView(context);
        LinearLayout.LayoutParams buttonParams =
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
        textView.setLayoutParams(buttonParams);
        textView.setText("请等待");

        ProgressBar progressBar = new ProgressBar(context);
        progressBar.setLayoutParams(
                new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));

        final Handler handler = new Handler();
        handler.postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                mockTool.mockClick(buttonConfirmPay);
            }
        }, 2000);
        handler.postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                hide();
            }
        }, 5000);

        rootLayout.addView(textView);
        rootLayout.addView(progressBar);
        return rootLayout;
    }

    public void hide()
    {
        try
        {
            //  TN对象中获得了show方法
            Method method = mTN.getClass().getDeclaredMethod("hide", null);
            method.setAccessible(true);
            //  调用show方法来显示Toast信息提示框
            method.invoke(mTN, null);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

DialogB和Toast中都实现了模拟点击,测试发现DialogB的时候下面的A是没有焦点的,而Toast的时候是有焦点的;
来看模拟点击和获取button的代码:

package com.amuro.dialogtopdemo;

import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * Created by Amuro on 16/7/23.
 */
public class MockTool
{
    public void mockClick(Button button)
    {
        int[] location = new int[2];
        button.getLocationOnScreen(location);
        long time = new Date().getTime();
        MotionEvent motionEvent =
                MotionEvent.obtain(
                        time,
                        time,
                        MotionEvent.ACTION_DOWN,
                        location[0] + 1,
                        location[0] + 1,
                        0);
        button.dispatchTouchEvent(motionEvent);
        motionEvent.recycle();

        motionEvent =
                MotionEvent.obtain(
                        time + 1,
                        time + 1,
                        MotionEvent.ACTION_UP,
                        location[0] + 1,
                        location[0] + 1,
                        0);
        button.dispatchTouchEvent(motionEvent);
        motionEvent.recycle();
    }

    private Button buttonConfirmPay;

    public Button getConfirmPayButton(Context context)
    {
        Field wmGlobalField = null;
        try
        {
            wmGlobalField =
                    context.getSystemService(Context.WINDOW_SERVICE).
                            getClass().getDeclaredField("mGlobal");
        }
        catch (Exception e)
        {
            wmGlobalField = null;
        }

        if (wmGlobalField != null)
        {
            try
            {
                wmGlobalField.setAccessible(true);
                Object wmGlobal = wmGlobalField.get(context.getSystemService(Context.WINDOW_SERVICE));

                Field viewsField = wmGlobal.getClass().getDeclaredField("mViews");
                viewsField.setAccessible(true);
                ArrayList<View> views = (ArrayList<View>) viewsField.get(wmGlobal);

                getConfirmPayButton(views);

                return buttonConfirmPay;
            }
            catch (Exception e)
            {
                e.printStackTrace();
                return null;
            }
        }
        else
        {
            try
            {
                Field wmLocalField =
                        context.getSystemService(Context.WINDOW_SERVICE).getClass().
                                getSuperclass().getDeclaredField("mWindowManager");
//                        context.getSystemService(Context.WINDOW_SERVICE).
//                        getClass().getDeclaredField("mWindowManager");
                wmLocalField.setAccessible(true);
                Object wmLocal =
                        wmLocalField.get(context.getSystemService(Context.WINDOW_SERVICE));

                Field viewsField = wmLocal.getClass().getDeclaredField("mViews");
                viewsField.setAccessible(true);
                List<View> viewList = Arrays.asList((View[])viewsField.get(wmLocal));

                ArrayList<View> views = new ArrayList<>();
                for(View view : viewList)
                {
                    views.add(view);
                }

                getConfirmPayButton(views);

                return buttonConfirmPay;
            }
            catch (Exception e)
            {
                return null;
            }
        }
    }

    private void getConfirmPayButton(ArrayList<View> views)
    {
        for (int i = 0; i < views.size(); i++)
        {
            ViewGroup viewGroup = (ViewGroup) views.get(i);
            traverse(viewGroup);

            if (buttonConfirmPay != null)
            {
                break;
            }
        }

    }

    private void traverse(ViewGroup viewGroup)
    {
        int count = viewGroup.getChildCount();

        for (int i = 0; i < count; i++)
        {
            View view = viewGroup.getChildAt(i);

            if (view instanceof Button)
            {
                if (((Button) view).getText().equals("Test"))
                {
                    buttonConfirmPay = (Button) view;
                    break;
                }
            }

            if (view instanceof ViewGroup)
            {
                traverse((ViewGroup) view);
            }
        }
    }
}

大量的反射,模拟点击的原理就是把down和up事件传给button,让button的onclick事件被触发;
从Window中获取button的原理也是从window中拿到所有被缓存的view,然后从中间找到你要的那个控件就行了。

就酱~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值