Android开发实现多进程弹窗效果
安卓开发之多进程弹窗,供大家参考,具体内容如下
背景
有时在弹窗绘图时,需要弹窗在新的进程中,以保证在弹窗绘图的过程中不会占用过多的内存导致主进程被关。
代码实现
子进程弹窗
首先我们需要一个透明的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开发实现的圆角按钮、文字阴影按钮效果示例
-
Android开发实现绘制淘宝收益图折线效果示例
-
Android控件gridview实现单行多列横向滚动效果
-
Android开发之自定义view实现通讯录列表A~Z字母提示效果【附demo源码下载】
-
Android开发中SpannableString的富文本显示效果代码实现
-
Android开发实现的自动换图片、轮播图效果示例
-
Android开发之ToggleButton实现开关效果示例
-
Android开发实现ListView点击展开收起效果示例
-
【Android 音视频开发打怪升级:OpenGL渲染视频画面篇】三、OpenGL渲染多视频,实现画中画
-
Android开发TextvView实现镂空字体效果示例代码