Launcher 卡片框架多模块集成
<p>方案一、aar架包集成</p><p>最简单直接的方案,卡片侧实现,打成aar包提供到launcher显示</p>
<p>方案二、<span data-font-family="default">AppWidget</span></p>
<p><span data-font-family="default">原生的桌面小组件方案,被限制无法自定义view</span></p>
<p><span data-font-family="default">底层通过BroadcastReceiver实现</span></p>
<h4><span data-font-family="default">方案三、插件方案</span></h4>
<p><span data-font-family="default">插件方案有好几种,实现原理都是通过配置实现,其中有Service,BroadcastReceiver,</span>Plugin</p>
<p>在SystemUI模块中,状态栏等模块很多使用的都是Plugin方案跟Service方案</p>
<p>这里详细讲通过<strong>Service配置跟Plugin配置</strong>实现</p>
<p>插件方案可以实现卡片跟launcher解耦,并且可以自定义view,还支持跨进程交互</p>
<p>首先定义一个插件,用于配置卡片信息,exported 属性标识可以给其它应用读取</p>
<div class="cnblogs_code"><img id="code_img_closed_d2782106-80a0-40bb-9376-47b519a73a6f" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_d2782106-80a0-40bb-9376-47b519a73a6f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_d2782106-80a0-40bb-9376-47b519a73a6f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">service
</span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">=".TestWidgetService"</span><span style="color: rgba(255, 0, 0, 1)">
android:exported</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
android:label</span><span style="color: rgba(0, 0, 255, 1)">="测试卡片1"</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">action </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.action.rear.APPWIDGET_PLUGIN"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">meta-data
</span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.provider"</span><span style="color: rgba(255, 0, 0, 1)">
android:resource</span><span style="color: rgba(0, 0, 255, 1)">="@xml/remote_control_widget_info"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">service</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">service
</span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">=".PagerWidgetPlugin"</span><span style="color: rgba(255, 0, 0, 1)">
android:exported</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
android:label</span><span style="color: rgba(0, 0, 255, 1)">="测试卡片2"</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">action </span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.action.rear.APPWIDGET_PLUGIN"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">intent-filter</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">meta-data
</span><span style="color: rgba(255, 0, 0, 1)">android:name</span><span style="color: rgba(0, 0, 255, 1)">="com.appwidget.provider"</span><span style="color: rgba(255, 0, 0, 1)">
android:resource</span><span style="color: rgba(0, 0, 255, 1)">="@xml/pager_widget_info"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">service</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_ab157e25-0682-47bd-9210-f3e949ca9917" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_ab157e25-0682-47bd-9210-f3e949ca9917" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_ab157e25-0682-47bd-9210-f3e949ca9917" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.example.page
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> Plugin {
fun onCreate(hostContext: Context, pluginContext: Context) {
}
fun onDestroy() {
}
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span> PagerWidgetPlugin : Plugin</pre>
</div>
<span class="cnblogs_code_collapse">Plugin</span></div>
<div class="cnblogs_code"><img id="code_img_closed_a79b9b59-c536-414d-b260-045d9c00766c" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a79b9b59-c536-414d-b260-045d9c00766c" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a79b9b59-c536-414d-b260-045d9c00766c" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.example.page
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.app.Service
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Intent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.os.IBinder
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestWidgetService : Service() {
override fun onBind(intent: Intent</span>?): IBinder?<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">Service</span></div>
<p>上面插件是直接定义在卡片里,其实应该在launcher中,然后对所有的卡片提供基础aar,统一接口</p>
<p>然后在res/xml下面新建 widget_info.xml</p>
<div class="cnblogs_code"><img id="code_img_closed_b2afd8fb-7d28-4db6-8658-846107abccea" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_b2afd8fb-7d28-4db6-8658-846107abccea" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_b2afd8fb-7d28-4db6-8658-846107abccea" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)"><?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">com-appwidget-provider
</span><span style="color: rgba(255, 0, 0, 1)">cardType</span><span style="color: rgba(0, 0, 255, 1)">="0"</span><span style="color: rgba(255, 0, 0, 1)">
mediumLayout</span><span style="color: rgba(0, 0, 255, 1)">="@layout/pager_control_layout"</span> <span style="color: rgba(0, 0, 255, 1)">/></span></pre>
</div>
<span class="cnblogs_code_collapse">pager_widget_info</span></div>
<div class="cnblogs_code"><img id="code_img_closed_75b1c9f9-bb87-47ad-9494-fa11a2c22b15" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_75b1c9f9-bb87-47ad-9494-fa11a2c22b15" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_75b1c9f9-bb87-47ad-9494-fa11a2c22b15" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)"><?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">com-appwidget-provider
</span><span style="color: rgba(255, 0, 0, 1)">cardType</span><span style="color: rgba(0, 0, 255, 1)">="0"</span><span style="color: rgba(255, 0, 0, 1)">
smallLayout</span><span style="color: rgba(0, 0, 255, 1)">="@layout/cards_remote_control_layout"</span> <span style="color: rgba(0, 0, 255, 1)">/></span></pre>
</div>
<span class="cnblogs_code_collapse">remote_control_widget_info</span></div>
<p>编写卡片布局</p>
<div class="cnblogs_code"><img id="code_img_closed_b30b0499-abb1-4762-951f-d7ce30ba2fef" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_b30b0499-abb1-4762-951f-d7ce30ba2fef" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_b30b0499-abb1-4762-951f-d7ce30ba2fef" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)"><?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
xmlns:app</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res-auto"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
android:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/white"</span><span style="color: rgba(255, 0, 0, 1)">
android:focusable</span><span style="color: rgba(0, 0, 255, 1)">="false"</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">ImageView
</span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_image"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">TextView
</span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_title"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_margin</span><span style="color: rgba(0, 0, 255, 1)">="32dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:drawableLeft</span><span style="color: rgba(0, 0, 255, 1)">="@mipmap/ic_launcher_round"</span><span style="color: rgba(255, 0, 0, 1)">
android:drawablePadding</span><span style="color: rgba(0, 0, 255, 1)">="8dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:text</span><span style="color: rgba(0, 0, 255, 1)">="title"</span><span style="color: rgba(255, 0, 0, 1)">
android:textColor</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/holo_blue_dark"</span><span style="color: rgba(255, 0, 0, 1)">
app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
app:layout_constraintTop_toTopOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">TextView
</span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_tips"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_marginBottom</span><span style="color: rgba(0, 0, 255, 1)">="4dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:ellipsize</span><span style="color: rgba(0, 0, 255, 1)">="end"</span><span style="color: rgba(255, 0, 0, 1)">
android:maxWidth</span><span style="color: rgba(0, 0, 255, 1)">="390dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:singleLine</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
android:text</span><span style="color: rgba(0, 0, 255, 1)">="tips"</span><span style="color: rgba(255, 0, 0, 1)">
android:textColor</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/holo_orange_dark"</span><span style="color: rgba(255, 0, 0, 1)">
app:layout_constraintBottom_toTopOf</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_summary"</span><span style="color: rgba(255, 0, 0, 1)">
app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_summary"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">TextView
</span><span style="color: rgba(255, 0, 0, 1)">android:id</span><span style="color: rgba(0, 0, 255, 1)">="@+id/card_remote_control_summary"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="wrap_content"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_marginStart</span><span style="color: rgba(0, 0, 255, 1)">="32dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_marginBottom</span><span style="color: rgba(0, 0, 255, 1)">="35dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:ellipsize</span><span style="color: rgba(0, 0, 255, 1)">="end"</span><span style="color: rgba(255, 0, 0, 1)">
android:maxWidth</span><span style="color: rgba(0, 0, 255, 1)">="405dp"</span><span style="color: rgba(255, 0, 0, 1)">
android:singleLine</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(255, 0, 0, 1)">
android:text</span><span style="color: rgba(0, 0, 255, 1)">="content"</span><span style="color: rgba(255, 0, 0, 1)">
android:textColor</span><span style="color: rgba(0, 0, 255, 1)">="@android:color/holo_blue_bright"</span><span style="color: rgba(255, 0, 0, 1)">
app:layout_constraintBottom_toBottomOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span><span style="color: rgba(255, 0, 0, 1)">
app:layout_constraintStart_toStartOf</span><span style="color: rgba(0, 0, 255, 1)">="parent"</span> <span style="color: rgba(0, 0, 255, 1)">/></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">androidx.constraintlayout.widget.ConstraintLayout</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<span class="cnblogs_code_collapse">cards_remote_control_layout</span></div>
<div class="cnblogs_code"><img id="code_img_closed_15213ad1-52a8-4016-8850-a2fd4548673e" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_15213ad1-52a8-4016-8850-a2fd4548673e" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_15213ad1-52a8-4016-8850-a2fd4548673e" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)"><?</span><span style="color: rgba(255, 0, 255, 1)">xml version="1.0" encoding="utf-8"</span><span style="color: rgba(0, 0, 255, 1)">?></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">com.example.page.loop.CustomViewPager </span><span style="color: rgba(255, 0, 0, 1)">xmlns:android</span><span style="color: rgba(0, 0, 255, 1)">="http://schemas.android.com/apk/res/android"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_width</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
android:layout_height</span><span style="color: rgba(0, 0, 255, 1)">="match_parent"</span><span style="color: rgba(255, 0, 0, 1)">
android:background</span><span style="color: rgba(0, 0, 255, 1)">="@color/white"</span> <span style="color: rgba(0, 0, 255, 1)">/></span></pre>
</div>
<span class="cnblogs_code_collapse">pager_control_layout</span></div>
<p>然后在launcher中,使用 AppWidgetManager 来读取配置信息</p>
<div class="cnblogs_code"><img id="code_img_closed_5e7433a4-4778-4d50-aae9-90aa75c4e923" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_5e7433a4-4778-4d50-aae9-90aa75c4e923" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_5e7433a4-4778-4d50-aae9-90aa75c4e923" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.test.launcher.rear.card.appwidget
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.annotation.SuppressLint
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.ComponentName
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Intent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.pm.PackageManager
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.pm.ResolveInfo
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.Log
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.blankj.utilcode.util.GsonUtils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.kunminx.architecture.ui.callback.UnPeekLiveData
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> org.xmlpull.v1.XmlPullParser
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> org.xmlpull.v1.XmlPullParserException
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.IOException
@SuppressLint(</span>"StaticFieldLeak"<span style="color: rgba(0, 0, 0, 1)">)
object AppWidgetManager {
val context: Context </span>=<span style="color: rgba(0, 0, 0, 1)"> android.app.AppGlobals.getInitialApplication()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val ACTION = "com.appwidget.action.rear.APPWIDGET_PLUGIN"
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val META_DATA_APPWIDGET_PROVIDER: String = "com.appwidget.provider"
<span style="color: rgba(0, 0, 255, 1)">private</span> val list = mutableListOf<CardModel><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> var mAppWidgetChangeListener: ((MutableList<CardModel>) -> Unit)? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
val showOnCards </span>= UnPeekLiveData(mutableListOf<CardModel><span style="color: rgba(0, 0, 0, 1)">())
init {
val intent </span>=<span style="color: rgba(0, 0, 0, 1)"> Intent(ACTION)
val resolveInfoList </span>=<span style="color: rgba(0, 0, 0, 1)"> context.packageManager.queryIntentServices(
intent,
PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES
)
Logger.d(</span>"resolveInfoList size ${resolveInfoList.size}"<span style="color: rgba(0, 0, 0, 1)">)
resolveInfoList.forEach { ri </span>-><span style="color: rgba(0, 0, 0, 1)">
parseAppWidgetProviderInfo(ri)
}
}
var id </span>= 0<span style="color: rgba(0, 0, 0, 1)">
fun allocateAppWidgetId(): Int {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> ++<span style="color: rgba(0, 0, 0, 1)">id
}
fun setAppWidgetChangeListener(listener: ((MutableList</span><CardModel>) -> Unit)?<span style="color: rgba(0, 0, 0, 1)">) {
mAppWidgetChangeListener </span>=<span style="color: rgba(0, 0, 0, 1)"> listener
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun parseAppWidgetProviderInfo(resolveInfo: ResolveInfo) {
val componentName </span>=<span style="color: rgba(0, 0, 0, 1)">
ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name)
val serviceInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> resolveInfo.serviceInfo
val hasXmlDefinition </span>= serviceInfo.metaData?.getInt(META_DATA_APPWIDGET_PROVIDER) != 0
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (hasXmlDefinition) {
val info </span>=<span style="color: rgba(0, 0, 0, 1)"> CardInfo()
info.serviceInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> serviceInfo
info.componentName </span>=<span style="color: rgba(0, 0, 0, 1)"> componentName
val pm </span>=<span style="color: rgba(0, 0, 0, 1)"> context.packageManager
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
serviceInfo.loadXmlMetaData(pm, META_DATA_APPWIDGET_PROVIDER).use { parser </span>->
<span style="color: rgba(0, 0, 255, 1)">if</span> (parser == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
Logger.w(</span>"$componentName parser is null"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
}
val nodeName: String </span>=<span style="color: rgba(0, 0, 0, 1)"> parser.name
</span><span style="color: rgba(0, 0, 255, 1)">if</span> ("com-appwidget-provider" !=<span style="color: rgba(0, 0, 0, 1)"> nodeName) {
Logger.w(</span>"$componentName provider is null"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
}
info.descriptionRes </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "description", 0<span style="color: rgba(0, 0, 0, 1)">)
info.mediumLayout </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "mediumLayout", 0<span style="color: rgba(0, 0, 0, 1)">)
info.mediumPreviewImage </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "mediumPreviewImage", 0<span style="color: rgba(0, 0, 0, 1)">)
info.smallLayout </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "smallLayout", 0<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (info.smallLayout != 0<span style="color: rgba(0, 0, 0, 1)">) {
info.sizeStyle </span>= 1<span style="color: rgba(0, 0, 0, 1)">
}
info.smallPreviewImage </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "smallPreviewImage", 0<span style="color: rgba(0, 0, 0, 1)">)
info.bigLayout </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "bigLayout", 0<span style="color: rgba(0, 0, 0, 1)">)
info.bigPreviewImage </span>=<span style="color: rgba(0, 0, 0, 1)">
parser.getAttributeResourceValue(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, "bigPreviewImage", 0<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (info.bigLayout != 0<span style="color: rgba(0, 0, 0, 1)">) {
info.sizeStyle </span>= 2<span style="color: rgba(0, 0, 0, 1)">
}
Logger.d(</span>"parseAppWidgetProviderInfo $componentName hasLayout=${info.hasLayout()}"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (info.hasLayout()) {
list.add(CardModel(allocateAppWidgetId(), info, </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">))
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
}
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: IOException) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Ok to catch Exception here, because anything going wrong because
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> of what a client process passes to us should not be fatal for the
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> system process.</span>
Logger.e("XML parsing failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: PackageManager.NameNotFoundException) {
Logger.e(</span>"XML parsing failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: XmlPullParserException) {
Logger.e(</span>"XML parsing failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
}
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>也可以通过加载器获取</p>
<div class="cnblogs_code"><img id="code_img_closed_60906fc8-6cee-470c-9008-f13067af4f0f" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_60906fc8-6cee-470c-9008-f13067af4f0f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_60906fc8-6cee-470c-9008-f13067af4f0f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun parseAppWidgetProviderInfo(resolveInfo: ResolveInfo) {
val componentName </span>=<span style="color: rgba(0, 0, 0, 1)">
ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name)
val serviceInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> resolveInfo.serviceInfo
val pluginContext </span>=<span style="color: rgba(0, 0, 0, 1)"> PluginContextWrapper.createFromPackage(serviceInfo.packageName)
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
val cardPlugin </span>=<span style="color: rgba(0, 0, 0, 1)"> Class.forName(
serviceInfo.name, </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, pluginContext.classLoader
).newInstance() as CardPlugin
cardPlugin.onCreate(context, pluginContext)
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: Exception) {
Log.w(TAG, </span>"parseAppWidgetProviderInfo failed for AppWidget provider $componentName"<span style="color: rgba(0, 0, 0, 1)">, e)
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>因为处于不用apk,所以加载卡片类,需要加载其他路径的类文件,需要把这个类文件路径加到自己的classloader</p>
<div class="cnblogs_code"><img id="code_img_closed_a8b76f1d-b2c3-479d-b558-46cc2f0411f0" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_a8b76f1d-b2c3-479d-b558-46cc2f0411f0" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_a8b76f1d-b2c3-479d-b558-46cc2f0411f0" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.test.carlauncher.cards.plugin
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.app.Application
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.ContextWrapper
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.text.TextUtils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> dalvik.system.PathClassLoader
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.File
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PluginContextWrapper(
base: Context,
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val classLoader: ClassLoader =<span style="color: rgba(0, 0, 0, 1)"> ClassLoaderFilter(base.classLoader)
) : ContextWrapper(base) {
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val application: Application by lazy {
PluginApplication(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val mInflater: LayoutInflater by lazy {
LayoutInflater.from(baseContext).cloneInContext(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
}
override fun getClassLoader(): ClassLoader {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> classLoader
}
override fun getApplicationContext(): Context {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> application
}
override fun getSystemService(name: String): Any {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (LAYOUT_INFLATER_SERVICE ==<span style="color: rgba(0, 0, 0, 1)"> name) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mInflater
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> baseContext.getSystemService(name)
}
override fun toString(): String {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> "${javaClass.name}@${Integer.toHexString(hashCode())}_$packageName"<span style="color: rgba(0, 0, 0, 1)">
}
companion object {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val contextMap = mutableMapOf<String, Context><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val methodSetOuterContext = Class.forName("android.app.ContextImpl"<span style="color: rgba(0, 0, 0, 1)">)
.getDeclaredMethod(</span>"setOuterContext", Context::<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.java).apply {
isAccessible </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun Context.setOuterContext(outContext: Context) {
methodSetOuterContext.invoke(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, outContext)
}
fun createFromPackage(packageName: String): Context {
val contextCache </span>=<span style="color: rgba(0, 0, 0, 1)"> contextMap.get(packageName)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (contextCache != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> contextCache
}
val hostContext: Context </span>=<span style="color: rgba(0, 0, 0, 1)"> android.app.AppGlobals.getInitialApplication()
val appInfo </span>= hostContext.packageManager.getApplicationInfo(packageName, 0<span style="color: rgba(0, 0, 0, 1)">)
val appContext: Context </span>=<span style="color: rgba(0, 0, 0, 1)"> hostContext.createApplicationContext(
appInfo,
CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY
)
val zipPaths </span>= mutableListOf<String><span style="color: rgba(0, 0, 0, 1)">()
val libPaths </span>= mutableListOf<String><span style="color: rgba(0, 0, 0, 1)">()
android.app.LoadedApk.makePaths(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, appInfo, zipPaths, libPaths);
val classLoader </span>=<span style="color: rgba(0, 0, 0, 1)"> PathClassLoader(
TextUtils.join(File.pathSeparator, zipPaths),
TextUtils.join(File.pathSeparator, libPaths),
ClassLoaderFilter(hostContext.classLoader)
)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册广播、绑定服务、startActivity会使用OuterContext
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> (appContext as android.app.ContextImpl).setOuterContext(context)</span>
<span style="color: rgba(0, 0, 0, 1)"> appContext.setOuterContext(hostContext)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> PluginContextWrapper(appContext, classLoader).also {
contextMap.put(packageName, it)
}
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_c36bc6ac-6318-47eb-8d5d-38533a1f62e9" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_c36bc6ac-6318-47eb-8d5d-38533a1f62e9" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_c36bc6ac-6318-47eb-8d5d-38533a1f62e9" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ClassLoaderFilter(
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> val mBase: ClassLoader,
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val mPackages: Array<String><span style="color: rgba(0, 0, 0, 1)">
) : ClassLoader(getSystemClassLoader()) {
@Throws(ClassNotFoundException::</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)
override fun loadClass(name: String, resolve: Boolean): Class</span><*><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (pkg in mPackages) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (name.startsWith(pkg)) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mBase.loadClass(name)
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.loadClass(name, resolve)
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_347f800a-187c-4e07-85ab-d7a5ebb1e0ee" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_347f800a-187c-4e07-85ab-d7a5ebb1e0ee" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_347f800a-187c-4e07-85ab-d7a5ebb1e0ee" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PluginApplication(context: Context) : Application() {
init {
attachBaseContext(context)
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>获取到卡片的context跟classloader后,传入到 PluginContextWrapper 中,用于后续卡片内加载布局</p>
<p>通过PathClassLoader构建的类加载器包含了插件APK的路径,当调用LayoutInflater.inflate()时,系统会通过getClassLoader()获取这个自定义加载器来实例化插件中的自定义View类</p>
<p>类中重写了 getSystemService(),返回自定义的LayoutInflater,这个inflater绑定了插件的Context,确保资源解析的正确性</p>
<p>setOuterContext()将宿主Context设置为OuterContext,这样在插件中启动Activity、注册广播等操作时,系统会使用宿主环境来执行这些跨进程操作</p>
<p>上面操作确保插件中的类加载、资源访问和组件交互都能在正确的环境中执行</p>
<div><span style="color: rgba(51, 51, 51, 1); font-family: "PingFang SC", Arial, sans-serif; font-size: 16px">接下来将卡片布局加载到统一的容器中,在容器内加载布局启动activity等操作都使用的卡片context</span></div>
<div>
<div class="cnblogs_code"><img id="code_img_closed_0dc12b15-80b5-4358-a4c0-467c7a591606" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_0dc12b15-80b5-4358-a4c0-467c7a591606" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_0dc12b15-80b5-4358-a4c0-467c7a591606" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.test.launcher.rear.card.appwidget
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.content.Context
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Color
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.AttributeSet
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.Display
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.Gravity
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.KeyEvent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.LayoutInflater
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.MotionEvent
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.View
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.view.ViewGroup
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.widget.FrameLayout
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.widget.TextView
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.view.children
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CardHostView @JvmOverloads constructor(
context: Context, attrs: AttributeSet</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
) : FrameLayout(context, attrs) {
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> lateinit var contentView: View
</span><span style="color: rgba(0, 0, 255, 1)">private</span> var decoratorView: View? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
var cardInfo: CardInfo</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
var initialLayout </span>= 0<span style="color: rgba(0, 0, 0, 1)">
set(value) {
field </span>=<span style="color: rgba(0, 0, 0, 1)"> value
apply()
}
fun apply() {
contentView </span>=<span style="color: rgba(0, 0, 0, 1)"> getDefaultView()
removeAllViews()
contentView.setCorner(getDimen(baseDimen.baseapp_auto_dp_32).toFloat())
addView(contentView, LayoutParams(</span>-1, -1<span style="color: rgba(0, 0, 0, 1)">))
}
fun getDefaultView(): View {
var defaultView: View</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span>
<span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
val layoutId: Int </span>=<span style="color: rgba(0, 0, 0, 1)"> initialLayout
defaultView </span>= LayoutInflater.from(context).inflate(layoutId, <span style="color: rgba(0, 0, 255, 1)">this</span>, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
setOnClickListener {
defaultView</span>?<span style="color: rgba(0, 0, 0, 1)">.callOnClick()
}
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (exception: RuntimeException) {
Logger.e(</span>"Error inflating AppWidget $cardInfo"<span style="color: rgba(0, 0, 0, 1)">, exception)
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (defaultView == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
Logger.w(</span>"getDefaultView couldn't find any view, so inflating error"<span style="color: rgba(0, 0, 0, 1)">)
defaultView </span>=<span style="color: rgba(0, 0, 0, 1)"> getErrorView()
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> defaultView
}
override fun dispatchKeyEvent(event: KeyEvent</span>?<span style="color: rgba(0, 0, 0, 1)">): Boolean {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> !(parentView()?.inEditeMode ?: <span style="color: rgba(0, 0, 255, 1)">false</span>) && <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.dispatchKeyEvent(event)
}
override fun dispatchTouchEvent(ev: MotionEvent</span>?<span style="color: rgba(0, 0, 0, 1)">): Boolean {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> !(parentView()?.inEditeMode ?: <span style="color: rgba(0, 0, 255, 1)">false</span>) && <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.dispatchTouchEvent(ev)
}
fun exitEditeMode() {
decoratorView</span>?<span style="color: rgba(0, 0, 0, 1)">.let {
removeView(it)
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun getErrorView(): View {
val tv </span>=<span style="color: rgba(0, 0, 0, 1)"> TextView(context)
tv.gravity </span>=<span style="color: rgba(0, 0, 0, 1)"> Gravity.CENTER
tv.setText(com.android.internal.R.string.gadget_host_error_inflating)
tv.setBackgroundColor(Color.argb(</span>127, 0, 0, 0<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> tv
}
fun getContentView(): View {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> contentView
}
override fun onAttachedToWindow() {
</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onAttachedToWindow()
Logger.d(</span>"${contentView::class.java.name}#${contentView.hashCode()} onAttachedToWindow"<span style="color: rgba(0, 0, 0, 1)">)
}
override fun onDetachedFromWindow() {
</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onDetachedFromWindow()
Logger.d(</span>"${contentView::class.java.name}#${contentView.hashCode()} onDetachedFromWindow"<span style="color: rgba(0, 0, 0, 1)">)
}
fun View.parentView() </span>= parent?.parent as?<span style="color: rgba(0, 0, 0, 1)"> FocusLimitRecycleView
companion object {
fun obtain(context: Context, card: CardModel): CardHostView {
val packageName </span>=<span style="color: rgba(0, 0, 0, 1)"> card.info.componentName.packageName
val pluginContext </span>=
<span style="color: rgba(0, 0, 255, 1)">if</span> (packageName == context.packageName) context <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
PluginContextWrapper.createFromPackage(packageName, context.display)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> CardHostView(pluginContext).also {
it.id </span>=<span style="color: rgba(0, 0, 0, 1)"> View.generateViewId()
it.isFocusable </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
it.cardInfo </span>=<span style="color: rgba(0, 0, 0, 1)"> card.info
it.initialLayout </span>=<span style="color: rgba(0, 0, 0, 1)"> when (card.info.sizeStyle) {
</span>1 -><span style="color: rgba(0, 0, 0, 1)"> card.info.smallLayout
</span>3 -><span style="color: rgba(0, 0, 0, 1)"> card.info.bigLayout
</span><span style="color: rgba(0, 0, 255, 1)">else</span> -><span style="color: rgba(0, 0, 0, 1)"> card.info.mediumLayout
}
}
}
}
open fun updateChildState(it: Boolean, recyclerView: FocusLimitRecycleView) {
val inTouchMode </span>=<span style="color: rgba(0, 0, 0, 1)"> recyclerView.isInTouchMode
val hasFocus </span>=<span style="color: rgba(0, 0, 0, 1)"> recyclerView.hasFocus()
val parent </span>= parent as?<span style="color: rgba(0, 0, 0, 1)"> ViewGroup
Logger.d(</span>"parent isInTouchMode $inTouchMode $hasFocus"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (it) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (hasFocus && !<span style="color: rgba(0, 0, 0, 1)">inTouchMode) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (recyclerView.getEditeChild() == parent?<span style="color: rgba(0, 0, 0, 1)">.tag) {
parent</span>?.descendantFocusability =<span style="color: rgba(0, 0, 0, 1)"> FOCUS_BLOCK_DESCENDANTS
getContentView().alpha </span>=<span style="color: rgba(0, 0, 0, 1)"> 1f
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
parent</span>?.descendantFocusability =<span style="color: rgba(0, 0, 0, 1)"> FOCUS_AFTER_DESCENDANTS
getContentView().alpha </span>= 0.4f<span style="color: rgba(0, 0, 0, 1)">
}
}
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
getContentView().alpha </span>=<span style="color: rgba(0, 0, 0, 1)"> 1f
parent</span>?<span style="color: rgba(0, 0, 0, 1)">.visible()
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>在launcher中直接 CardHostView.obtain(mBinding.root.context,it) 创建卡片显示在桌面 </p>
</div><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19174303
頁:
[1]