欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发实现多进程弹窗效果

程序员文章站 2022-07-05 21:49:44
安卓开发之多进程弹窗,供大家参考,具体内容如下背景有时在弹窗绘图时,需要弹窗在新的进程中,以保证在弹窗绘图的过程中不会占用过多的内存导致主进程被关。代码实现子进程弹窗首先我们需要一个透明的activi...

安卓开发之多进程弹窗,供大家参考,具体内容如下

背景

有时在弹窗绘图时,需要弹窗在新的进程中,以保证在弹窗绘图的过程中不会占用过多的内存导致主进程被关。

代码实现

子进程弹窗

首先我们需要一个透明的activity来作为弹窗展示,并且这个透明activity就存在于子进程中,这一切都可以在清单文件中实现:

<activity
    android:name=".processactivity"
    android:process=":process_test"
    android:theme="@style/translucentstyle" />

使用到的主题定义在res/values/themes.xml中:

<style name="translucentstyle" parent="theme.appcompat.light.noactionbar">
    <item name="android:windowbackground">@android:color/transparent</item> <!-- 背景色透明 -->
    <item name="android:windowistranslucent">true</item> <!-- 是否有透明属性 -->
    <item name="android:backgrounddimenabled">false</item> <!-- 背景是否半透明 -->
    <item name="android:windowanimationstyle">@android:style/animation.translucent</item> <!-- activity窗口切换效果 -->
</style>

而后设置activity的位置和尺寸:

public class processactivity extends activity {
    ...
 
    @suppresslint("clickableviewaccessibility")
    @override
    protected void oncreate(@nullable bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_process);
 
        ...
 
        windowmanager.layoutparams lp = getwindow().getattributes();
        lp.width = 950;
        lp.height = 1700;
        lp.gravity = gravity.start;
        getwindow().setattributes(lp);
 
        ...
    }
    ...
}

使用到的布局文件activity_process.xml如下所示:

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_process"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200"
    android:orientation="vertical">
 
    <button
        android:id="@+id/btn_process"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test sub process" />
 
    <button
        android:id="@+id/btn_finish"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margintop="5dp"
        android:text="finish sub process" />
 
</linearlayout>

背景色为青色,两个button,一个负责展示toast,一个负责结束这个弹窗,我们在oncreate()中为它们添加点击事件监听:

button button_process = findviewbyid(r.id.btn_process);
button button_finish = findviewbyid(r.id.btn_finish);
 
button_process.setonclicklistener(v -> {
    toast.maketext(this, "oncreate: button in sub process has been clicked.", toast.length_short).show();
});
button_finish.setonclicklistener(v -> {
    processactivity.this.finish();
});

接下来要实现的是事件透传:因为子进程窗口是一个弹窗,当没有触摸到弹窗中可点击组件时,应该由下面的activity去承接触摸事件,这部分逻辑的实现如下所示:

public class processactivity extends activity {
    private view mrootview;
 
    @suppresslint("clickableviewaccessibility")
    @override
    protected void oncreate(@nullable bundle savedinstancestate) {
        mrootview = layoutinflater.from(this).inflate(r.layout.activity_process, null);
        setcontentview(mrootview);
        ...
 
        button button_process = findviewbyid(r.id.btn_process);
        button button_finish = findviewbyid(r.id.btn_finish);
 
        ...
    }
 
    @override
    public boolean dispatchtouchevent(motionevent event) {
        if (event.getaction() == motionevent.action_down) {
            view target = utils.getviewtouchedbyevent(mrootview, event);
            if (target != null) {
                target.dispatchtouchevent(event);
                return true;
            }
        }
 
        intent intent = new intent();
        intent.setaction("touchevent");
        intent.putextra("event", event);
        sendbroadcast(intent);
        return super.dispatchtouchevent(event);
    }
}

因为弹窗窗口和主窗口位于两个进程中,因此触摸事件的传递需要用ipc方式,这里采用的是广播。utils.isdebugwindowvalidtouched()负责判断当前点击事件是否点到了某个可点击的控件,方法代码如下:

public static view getviewtouchedbyevent(view view, motionevent event) {
    if (view == null || event == null) {
        return null;
    }
 
    if (!(view instanceof viewgroup)) {
        return isdebugwindowvalidtouched(view, event) ? view : null;
    }
 
    viewgroup parent = ((viewgroup) view);
    int childrencount = parent.getchildcount();
    for (int i = 0; i < childrencount; i++) {
        view target = getviewtouchedbyevent(parent.getchildat(i), event);
        if (target != null) {
            return target;
        }
    }
    return null;
}
 
private static boolean isdebugwindowvalidtouched(view view, motionevent event) {
    if (event == null || view == null) {
        return false;
    }
 
    if (view.getvisibility() != view.visible) {
        return false;
    }
 
    final float eventrawx = event.getrawx(); // 获取event在屏幕上的坐标
    final float eventrawy = event.getrawy();
 
    rectf rect = new rectf();
    int[] location = new int[2];
    view.getlocationonscreen(location); // 获取view在屏幕上的坐标位置
 
    float x = location[0];
    float y = location[1];
    rect.left = x;
    rect.right = x + view.getwidth();
    rect.top = y;
    rect.bottom = y + view.getheight();
 
 
    return rect.contains(eventrawx, eventrawy);
}

子进程弹窗窗口processactivity的完整代码如下所示:

package com.example.testrxjava;
 
import android.annotation.suppresslint;
import android.app.activity;
import android.content.intent;
import android.os.bundle;
import android.os.process;
import android.util.log;
import android.view.gravity;
import android.view.layoutinflater;
import android.view.motionevent;
import android.view.view;
import android.view.viewgroup;
import android.view.windowmanager;
import android.widget.button;
import android.widget.compoundbutton;
import android.widget.switch;
import android.widget.textview;
import android.widget.toast;
import android.widget.togglebutton;
 
import androidx.annotation.nullable;
 
import java.util.arraylist;
import java.util.list;
 
public class processactivity extends activity {
    public static final string tag = "processactivity";
    private view mrootview;
 
    @suppresslint("clickableviewaccessibility")
    @override
    protected void oncreate(@nullable bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        mrootview = layoutinflater.from(this).inflate(r.layout.activity_process, null);
        setcontentview(mrootview);
        log.i(tag, "oncreate: pid = " + process.mypid());
 
        button button_process = findviewbyid(r.id.btn_process);
        textview button_finish = findviewbyid(r.id.btn_finish);
 
        button_process.setonclicklistener(v -> {
            toast.maketext(this, "oncreate: button in sub process has been clicked.", toast.length_short).show();
        });
        button_finish.setonclicklistener(v -> {
            processactivity.this.finish();
        });
 
        togglebutton togglebutton = findviewbyid(r.id.toggle);
        togglebutton.setoncheckedchangelistener((buttonview, ischecked) -> toast.maketext(processactivity.this,
                "toggle button in sub process has been clicked, current state of checking is: " + ischecked,
                toast.length_short).show());
 
        switch switch_button = findviewbyid(r.id.switch_sub_process);
        switch_button.setoncheckedchangelistener((buttonview, ischecked) -> toast.maketext(processactivity.this,
                "switch in sub process has been clicked, current state of checking is: " + ischecked,
                toast.length_short).show());
 
 
        windowmanager.layoutparams lp = getwindow().getattributes();
        lp.width = 950;
        lp.height = 1700;
        lp.gravity = gravity.start;
        getwindow().setattributes(lp);
    }
 
    @override
    public boolean dispatchtouchevent(motionevent event) {
        if (event.getaction() == motionevent.action_down) {
            view target = utils.getviewtouchedbyevent(mrootview, event);
            if (target != null) {
                target.dispatchtouchevent(event);
                return true;
            }
        }
 
        intent intent = new intent();
        intent.setaction("touchevent");
        intent.putextra("event", event);
        sendbroadcast(intent);
        return super.dispatchtouchevent(event);
    }
}

主界面

回到主界面,首先需要接收一下touchevent这个广播:

public class mainactivity extends appcompatactivity {
    ...
    private broadcastreceiver mreceiver;
 
    @override
    protected void oncreate(bundle savedinstancestate) {
        ...
        mreceiver = new broadcastreceiver() {
            @override
            public void onreceive(context context, intent intent) {
                if (intent.getaction().equals("touchevent")) {
                    motionevent event = intent.getparcelableextra("event");
                    try {
                        class popupclass = class.forname("android.widget.popupwindow");
                        field decorviewfield = popupclass.getdeclaredfield("mdecorview");
                        decorviewfield.setaccessible(true);
                        object decorview = decorviewfield.get(window);
 
                        method dispatchtouchevent = decorview.getclass().getdeclaredmethod("dispatchtouchevent", motionevent.class);
                        dispatchtouchevent.invoke(decorview, event);
 
                    } catch (exception e) {
                        e.printstacktrace();
                    }
                }
            }
        };
 
        intentfilter filter = new intentfilter("touchevent");
        registerreceiver(mreceiver, filter);
    }
 
    @override
    protected void ondestroy() {
        super.ondestroy();
        unregisterreceiver(mreceiver);
    }
}

因为主界面中也有一个弹窗,因此当触摸事件从子进程传过来的时候,需要主进程的弹窗去处理,因此在onreceive()方法中通过反射执行了主进程弹窗的mdecorview的dispatchtouchevent()方法去传递触摸事件,mainactivity的完整代码如下所示:

package com.example.testrxjava;
 
import android.content.broadcastreceiver;
import android.content.context;
import android.content.intent;
import android.content.intentfilter;
import android.graphics.rectf;
import android.os.bundle;
import android.os.parcelable;
import android.util.log;
import android.view.gravity;
import android.view.layoutinflater;
import android.view.motionevent;
import android.view.view;
import android.view.viewgroup;
import android.widget.button;
import android.widget.listview;
import android.widget.popupwindow;
import android.widget.textview;
 
import androidx.appcompat.app.appcompatactivity;
 
import java.lang.reflect.field;
import java.lang.reflect.method;
import java.util.arraylist;
import java.util.list;
 
public class mainactivity extends appcompatactivity {
    private button mbutton;
    private button mhide;
    private broadcastreceiver mreceiver;
 
    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_main);
        textview textview = findviewbyid(r.id.text_view);
        listview listview = findviewbyid(r.id.list);
        arraylist<string> list = new arraylist<>();
        for (int i = 0; i < 200; i++) {
            list.add("no." + i);
        }
        myadapter adapter = new myadapter(list, this);
 
        listview.setadapter(adapter);
 
        adapter.notifydatasetchanged();
 
        popupwindow window = new popupwindow(this);
        view windowview = layoutinflater.from(this).inflate(r.layout.window_layout, null);
 
        mbutton = windowview.findviewbyid(r.id.btn_window);
        mbutton.setonclicklistener(view -> {
            startactivity(new intent(mainactivity.this, processactivity.class));
        });
 
        mhide = windowview.findviewbyid(r.id.btn_hide);
        mhide.setonclicklistener(v -> {
            mbutton.setvisibility(mbutton.getvisibility() == view.visible ? view.gone : view.visible);
        });
 
        window.settouchinterceptor((view, motionevent) -> {
            if (motionevent.getaction() == motionevent.action_down) {
                view target = utils.getviewtouchedbyevent(windowview, motionevent);
                if (target != null) {
                    target.dispatchtouchevent(motionevent);
                    return true;
                }
            }
            mainactivity.this.dispatchtouchevent(motionevent);
            return false;
        });
 
        view rootview = getwindow().getdecorview();
        window.setoutsidetouchable(false);
        window.setondismisslistener(() -> textview.post(() -> {
            window.showatlocation(rootview, gravity.bottom, 0, 0);
        }));
        window.setcontentview(windowview);
        window.setwidth(viewgroup.layoutparams.match_parent);
        window.setheight(viewgroup.layoutparams.wrap_content);
 
        findviewbyid(r.id.root).setonclicklistener(v -> {
            log.i("mainactivity", "touch event gets to text view!");
        });
 
        textview.post(() -> {
            window.showatlocation(rootview, gravity.bottom, 0, 0);
        });
 
        mreceiver = new broadcastreceiver() {
            @override
            public void onreceive(context context, intent intent) {
                if (intent.getaction().equals("touchevent")) {
                    motionevent event = intent.getparcelableextra("event");
                    try {
                        class popupclass = class.forname("android.widget.popupwindow");
                        field decorviewfield = popupclass.getdeclaredfield("mdecorview");
                        decorviewfield.setaccessible(true);
                        object decorview = decorviewfield.get(window);
 
                        method dispatchtouchevent = decorview.getclass().getdeclaredmethod("dispatchtouchevent", motionevent.class);
                        dispatchtouchevent.invoke(decorview, event);
                    } catch (exception e) {
                        e.printstacktrace();
                    }
                }
            }
        };
 
        intentfilter filter = new intentfilter("touchevent");
        registerreceiver(mreceiver, filter);
    }
 
    @override
    protected void ondestroy() {
        super.ondestroy();
        unregisterreceiver(mreceiver);
    }
}

效果

Android开发实现多进程弹窗效果

背景紫色的是主进程的弹窗,青色的是子进程的弹窗。从录像中可以看到,当按下事件按到位于上层的组件时,上层的组件会响应;如果按到了上层弹窗的空白处,触摸事件则会向下传递。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。