【版权声明】本文原发布于上海竖排网络科技有限公司官方知乎账号,由本人撰写发布,现摘抄复习,有什么问题可以直接来我掘金提问。
【前言】Android 从4.2开始支持双屏显示,支持版本为17以上。
Android 双屏原理说白了,自定义一个Presentation类,Android 的标准实现是使用 API Presentation 来实现异显的功能。 Presentation 是扩展自 dialog.相当于一个升级版的弹窗,其实还是在同一个activity里面完成的,两个屏幕里面的内容,可以通过同一个activity进行控制和相关数据变动展示。在副屏上显示不同内容。它的显示内容是依附在主屏的Activity上的,如果Activity被销毁Presentation也不会再显示,主副屏内容会再次恢复成相同的页面。
【作者】清泓
官方文档:
直接上核心代码,在这里有几个需要注意的点,在用户更新界面UI的时候,需要确定Presentation是否处于开启状态中,如果处于存在的状态,直接更新UI即可,在用户多次触发双屏异显的时候,保证Presentation只有一个,不重复创建。至于对非activity的动态UI更新,我直接用的Rxbus.
Presentation继承自Dialog,获取到Presentation要显示的设备后,就要将Activity的context对象和设备信息作为参数来创建Presentation对象;将设备记录在成员变量mDisplay中,将Presentation设置为不可在外部点击取消;所以我封装了一下。以下你可以直接把需要的值带入进去。
**
* 双屏异显
* 2020年12月9日
*/
@SuppressLint("NewApi")
private void setCustomerProductList(String url) {
DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
if (presentationDisplays.length>1){
Log.e(TAG, "setCustomerProductList: "+"小屏弹窗超出数量,会造成返回的时候程序奔溃");
return;
}else {
Display display = presentationDisplays[0];
if (presentation != null) {
// presentation.dismiss();
// presentation = null;
// Log.e(TAG, "setCustomerProductList: "+"清空" );
Log.e(TAG, "setCustomerProductList: "+"直接传值!" );
RxBus.get().send(ConStant.DOUBLE_SCRENN_UI, mDoubleScreenUrl);
}else {
Log.e(TAG, "setCustomerProductList: "+"双屏实例为空启用初始化!" );
presentation = new DifferentDislay(this, display,url);
presentation.show();
RxBus.get().send(ConStant.DOUBLE_SCRENN_UI, mDoubleScreenUrl);
}
}
}
副屏,也就是Presentation 中,我们通过一个webView对接收到的内容,触发的内容进行展示。在将presentation显示出来之前,最重要的事情就是选择要将presentation显示在哪个设备上。要选择显示在哪个设备可能是一件非常困难的事情,因为可能此时系统中有多个显示设备。应用程序应该让系统选择合适的Display,而不是试图猜测哪个显示最佳。Android系统为我们提供了两种方式选择Display,我用的DisplayMannager。
/**
* Author:QingHong
* Data:2020年12月8日
*/
public class DifferentDislay extends Presentation {
private FrameLayout frameScreen;
private WebView mWebView;
private BroadcastReceiver MyReceiver;
private Context context;
private ScreenDoubleLiveData mutableLiveData;
private String mDoubleScreen;
public DifferentDislay(Context outerContext, Display display , String url) {
super(outerContext, display);
// mDoubleScreen=url;
context=App.getContext();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.double_display_block_test);
frameScreen =findViewById(R.id.double_screen_frame);
// 接收通知
RxBus.get().register(this);
// new ViewModelProvider(this,new CurrentConversationsViewModel.Factory(withWho)).get(CurrentConversationsViewModel.class);
// mutableLiveData = ViewModelProviders.of(App.getContext()).get(ScreenDoubleLiveData.class);
// MutableLiveData<String> urlEvent = mutableLiveData.getScreenContextEvent();
// if (SharedPreferencesUtil.getInstance(App.getContext()).getDoubleScreenUrl()==""){
// addWeb("https://www.bookstack.cn/");
// }else {
// addWeb(SharedPreferencesUtil.getInstance(App.getContext()).getDoubleScreenUrl());
// }
}
@Subscribe(code = ConStant.DOUBLE_SCRENN_UI, threadMode = ThreadMode.MAIN)
public void progressChange(String url) {
// mDoubleScreen=url;
Log.e(TAG, "progressChange: "+"走入此方法:双屏异显" );
if (url!=null){
// showProgressDialog("xx卡丁车收银系统","正在生成订单,请稍候~");
addWeb(url);
// hideProgressDialog();
}else {
Log.e(TAG, "onCreate: "+"无法接收到值");
Toast.makeText(context, "无法接收到值", Toast.LENGTH_SHORT).show();
}
}
public void addWeb(String url) {
// 避免内存泄漏,上下文用ApplicationContext.
WebView webView = new WebView(App.getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
webView.setLayoutParams(params);
WebSettings settings = webView.getSettings();
// 设置缓存模式
if (NetUtils.isNetworkAvailable(App.getContext())){
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
}else {
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
// 启用 WebView 调试模式。
// 注意:请勿在实际 App 中打开!
// webView.setWebContentsDebuggingEnabled(true);
// 启用二方/三方 Cookie 存储和 DOM Storage
// 注意:若要在实际 App 中使用,请先了解相关设置项细节。
// CookieManager.getInstance().setAcceptCookie(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
}
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setUseWideViewPort(true);//将图片调整到适合webview的大小
settings.setLoadWithOverviewMode(true);//缩放至屏幕的大小
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webView.loadUrl(url);
// webView.setWebViewClient(
// new WebViewClient() {
// @Override
// public boolean shouldOverrideUrlLoading(WebView view, String url) {
// view.loadUrl( url );
// return true;
// }
//
// @Override
// public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// handler.proceed(); //表示等待证书响应
// }
//
// //设置加载前的函数
// @Override
// public void onPageStarted(WebView view, String url, Bitmap favicon) {
//
// }
//
// //设置结束加载函数
// @Override
// public void onPageFinished(WebView view, String url) {
// //super.onPageFinished( view, null );
//// makeToastByHandlerPost("SHOW_DOWN_DIALOG_MS");
// }
// }
// );
mWebView = webView;
frameScreen.addView(mWebView);
}
@Override
public void setOnDismissListener(@Nullable OnDismissListener listener) {
super.setOnDismissListener(listener);
RxBus.get().unRegister(this);
}
@Override
protected void onStop() {
super.onStop();
RxBus.get().unRegister(this);
}
public static class Myreceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Handler handler;
Message message = null;
//获取Intent中携带的数据
Bundle bundle = intent.getExtras();
if(bundle != null) {
// int num1 = bundle.getInt("num1");
final String url = bundle.getString("url");
Log.i("IT_Real", "onReceive1: url = " + url);
// addWeb(url);
Toast.makeText(context, url, Toast.LENGTH_SHORT).show();
}
}
}
// public class MyReceiver1 extends BroadcastReceiver {
// public MyReceiver1() {
// }
//
// /**
// * 实现该方法即可,系统会自动调用处理
// * @param context 上下文
// * @param intent 对应的Intent
// */
// @Override
// public void onReceive(Context context, Intent intent) {
// Handler handler;
// Message message = null;
// //获取Intent中携带的数据
//
// Bundle bundle = intent.getExtras();
// if(bundle != null){
//// int num1 = bundle.getInt("num1");
// final String url = bundle.getString("url");
//
// Log.i("IT_Real", "onReceive1: url = " + url);
// Toast.makeText(context, url, Toast.LENGTH_SHORT).show();
//// makeToastByHandlerPost(url);
//
//
// }
// }
// }
//方法二:通过handler.post,更新UI主线程
private void makeToastByHandlerPost( final String msg){
Handler handler;
handler =new Handler();
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(App.getContext(), msg, Toast.LENGTH_SHORT).show();
// if (msg=="SHOW_DIALOG_MSG"){
// showProgressDialog("跑跑卡丁车...","结账中~");
// }else if (msg=="SHOW_DOWN_DIALOG_MS"){
// hideProgressDialog();
// }
}
});
}
}
其余补充:
以下是我用到的权限。
<!-- 2020年12月8日 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
动态更新传值部分:(handler机制)
// 加载的URL
private String mDoubleScreenUrl =null;
//在静态常量类里面定义的消息序列
public static final int DOUBLE_SCRENN_UI=2344;
/**
/*在主activity中拿动态数据的时候赋值
/*硬件对接
/*当数据不为空的时候,发送消息通知给主线程,进行UI更新。
/**
final String mDoubleScreenTest=returnDataBean.getZhengshi_banben().getVersion_remark_020();
Log.e(TAG, "mDoubleScreen: "+mDoubleScreenTest);
if (mDoubleScreenTest!=null){
mDoubleScreenUrl=mDoubleScreenTest;
webhandler.sendEmptyMessage(ConStant.DOUBLE_SCRENN_UI);
}
//在handler里面的我的方法,整个handler就不在这里截取了,这个大家应该都知道写,
只是我的思路大家可以参考一下
case ConStant.DOUBLE_SCRENN_UI:
showProgressDialog("跑跑卡丁车","正在结算,清稍候~");
setCustomerProductList(mDoubleScreenUrl);
webhandler.removeMessages(ConStant.DOUBLE_SCRENN_UI);
hideProgressDialog();
break;
通过Rxbus把值传送到Presentation里面。
有其他问题,请私信我哦,讲述的不够清楚的地方请联系我修正。
初稿待更新.........