Slice 是一种界面模板,可以在 Google 搜索应用中以及 Google 助理中等其他位置显示自己应用中的丰富而动态的互动内容。同时可以帮助用户更快地执行任务。借助Slice,开发者可以根据应用的设计自定义 Slice 的文字、图像等。
首先提供Slice在build.gradle里面添加如下依赖
def slice_version = "1.1.0-alpha01"
implementation "androidx.slice:slice-core:$slice_version"
implementation "androidx.slice:slice-builders:$slice_version"
然后打开Android Studio 项目,右键点击软件包,然后依次选择 【New】–> 【Other】 --> 【Slice Provider】,如下图所示
这样就创建出来一个MySliceProvider的类,打开AndroidManifest.xml 变化如下
每个Slice都有一个关联的URI,当其他应用界面想要展示Slice时,他会通过该URI来发送绑定请求,比如上面创建出来的SliceProvider的URI为"content:/com.example.slicedemo/";然后通过SliceProvider的onBindSlice方法处理该请求,并动态构建Slice,界面随后根据情况显示Slice。新建类继承SliceProvider,并重写onBindSlice()方法,在该方法里可以编写Slice展示模块中的相关逻辑代码。打开onBindSlice 方法,代码如下:
/**
* Construct the Slice and bind data if available.
*/
@SuppressLint({"ResourceType", "Slices"})
public Slice onBindSlice(@NonNull Uri sliceUri) {
Log.d(TAG, "onBindSlice");
Context context = getContext();
SliceAction activityAction = createOpenAction();
if (context == null) {
return null;
}
if (sliceUri.getPath().equals("/getSlice")) {
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.addRow(new RowBuilder()
.setTitle("Get Slice")
.addEndItem(IconCompat.createWithResource(getContext(), R.mipmap.ic_more), ListBuilder.SMALL_IMAGE)
.setPrimaryAction(activityAction))
.build();
} else {
// Error: Path not found.
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.addRow(new RowBuilder()
.setTitle("URI not found.")
.setPrimaryAction(activityAction)).build();
}
}
然后在使用slice使用方通过URI发送绑定请求,主要代码如下:
SliceView sliceView = findViewById(R.id.slice_view);
Uri uri = Uri.parse("content:/com.example.slicedemo/getSlice");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
LiveData sliceLiveData = SliceLiveData.fromIntent(this, intent);
sliceLiveData.removeObservers(this);
sliceLiveData.observe(this, slice -> {
if (slice == null) {
return;
}
sliceView.setSlice(slice);
});
与通知类似,如需处理 Slice 中的点按操作,我们可以附加在用户互动时触发的 PendingIntent 对象,比如点击Slice模块打开宿主App,我们打开MySliceProvider,然后在onBindSlice()方法中调用如下代码
//设置Action
private SliceAction createOpenAction() {
ComponentName cn = new ComponentName("com.hryt.hiphiplay", "com.hryt.hiphiplay.view.MainActivity");
Intent intent = new Intent();
intent.setComponent(cn);
return SliceAction.create(PendingIntent.getActivity(getContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT), IconCompat.createWithResource(getContext(),
R.mipmap.ic_more), ListBuilder.ICON_IMAGE, "open HiPhiPlay");
}
效果如下:
如果要对Slice进行测试,可以直接安装Google的Slice Viewer(下载链接)应用,该应用可以模拟Slice最终将如何出现在使用方应用中,下载slice-viewer.apk之后,我们在所在的目录中运行以下命令将 Slice 查看器安装到设备上;
adb install -r -t slice-viewer.apk
启动Slice Viewer之后,就可以输入Uri查看效果
Slice是通过ListBuilder类来创建的,在ListBuilder中,我们可以添加不同类型的行模块在应用中进行展示。
HeaderBuilder 主要为模板设置标头,标头可以支持标题,副标题,摘要副标题
代码如下:
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.setHeader(new ListBuilder.HeaderBuilder()
.setTitle("Header")
.setSubtitle("subTitle"))
.addRow(new RowBuilder()
.setTitle("Get Slice")
.addEndItem(IconCompat.createWithResource(getContext(), R.mipmap.ic_more), ListBuilder.SMALL_IMAGE)
.setPrimaryAction(activityAction))
.build();
效果如下:
我们可以使用 RowBuilder 构造一行内容,行可支持标题,副标题,图标等,也可以设置SliceAction;
代码如下:
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.setHeader(new ListBuilder.HeaderBuilder()
.setTitle("Header")
.setSubtitle("subTitle"))
.addRow(new RowBuilder()
.setTitle("Get Slice")
.addEndItem(IconCompat.createWithResource(getContext(), R.mipmap.ic_more), ListBuilder.SMALL_IMAGE)
.setPrimaryAction(activityAction))
.build();
效果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/c2b2d34ad6bd4be0a7a27087692dd988.png
使用 GridBuilder 构造内容网格,网格单元格是使用 CellBuilder 构造的。一个单元格最多可以支持两行文字和一张图片。
代码如下:
return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
.setHeader(new ListBuilder.HeaderBuilder().setTitle("Title")
.setSubtitle("subTitle"))
.addGridRow(new GridRowBuilder()
.addCell(new GridRowBuilder.CellBuilder()
.addText("网格1")
.setSliceAction(activityAction)
.addImage(IconCompat.createWithResource(getContext(), R.mipmap.demo), ListBuilder.SMALL_IMAGE))
.addCell(new GridRowBuilder.CellBuilder()
.addText("网格2")
.setSliceAction(activityAction)
.addImage(IconCompat.createWithResource(getContext(), R.mipmap.demo), ListBuilder.SMALL_IMAGE))
.setPrimaryAction(activityAction))
.build();
效果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/b8387b31817e40abb231da544c602da6.png
SliceProvider继承于ContentProvider,其APP间数据的传递通过ContentProvider的方式,应用APP向使用方APP提供其对应Slice的Uri,然后将数据封装成Slice对象通过Parcelable序列化的方式实现APP之间的数据传递。
在onBindSlice方法中通过ListBuider构建slice,可以添加各种模板比如RowBuilder,HeaderBuilder等,以addRow为例:
public ListBuilder addRow(@NonNull RowBuilder builder) {
mImpl.addRow(builder);
return this;
}
mImpl为ListBuilderImpl,接着看ListBuilderImpl的addRow方法
public void addRow(@NonNull RowBuilder builder) {
RowBuilderImpl impl =new RowBuilderImpl(createChildBuilder()); //括号这里给ListBuilderImpl创建了一个Slice.builder;
impl.fillFrom(builder);
...
addRow(impl); //把这一行内容添加到ListBuilder中
}
再看RowBuilderImpl的fillFrom方法:
void fillFrom(RowBuilder builder) {
if (builder.getUri() != null) {
setBuilder(new Slice.Builder(builder.getUri())); //给RowBuilderImpl创建了一个Slice.Buider
}
setPrimaryAction(builder.getPrimaryAction()); //给RowBuilderImpl设置Action
if (builder.getLayoutDirection() != -1) {
setLayoutDirection(builder.getLayoutDirection()); //给RowBuilderImpl设置布局方向
}
if (builder.getTitleAction() != null || builder.isTitleActionLoading()) {
setTitleItem(builder.getTitleAction(), builder.isTitleActionLoading());
} else if (builder.getTitleIcon() != null || builder.isTitleItemLoading()) {
setTitleItem(builder.getTitleIcon(), builder.getTitleImageMode(),
builder.isTitleItemLoading());
}
if (builder.getTitle() != null || builder.isTitleLoading()) { //设置TiTle
setTitle(builder.getTitle(), builder.isTitleLoading());
}
if (builder.getSubtitle() != null || builder.isSubtitleLoading()) {
setSubtitle(builder.getSubtitle(), builder.isSubtitleLoading()); //设置subTitle
}
if (builder.getContentDescription() != null) {
setContentDescription(builder.getContentDescription()); //内容描述
}
.......
}
这一步就是将我们在addRow时RowBuilder添加的属性设置给这个RowBuilderImpl,最后将这一行添加到ListBuilder中
@NonNull
public void addRow(@NonNull RowBuilderImpl builder) {
...
getBuilder().addSubSlice(builder.build());//这里的getBuilder就是在上一步addRow中给ListBuilder创建的Slice.Builder
}
RowBuilderIml继承自TemplateBuilderImpl,所以查看TemplateBuilderImpl的build()
public Slice build() {
...
apply(mSliceBuilder);//这里的mSliceBuilder就是fillFrom里给RowBuilderImpl创建的Slice.Builder
return mSliceBuilder.build();
} //看到在TemplateBuilderImpl的build()中调用了apply方法
@Override
public void apply(@NonNull Slice.Builder b) {
if (mStartItem != null) {
b.addSubSlice(mStartItem);
}
if (mTitleItem != null) {
b.addItem(mTitleItem);
}
if (mSubtitleItem != null) {
b.addItem(mSubtitleItem);
}
for (int i = 0; i < mEndItems.size(); i++) {
Slice item = mEndItems.get(i);
b.addSubSlice(item);
}
if (mContentDescr != null) {
b.addText(mContentDescr, SUBTYPE_CONTENT_DESCRIPTION);
}
if (mPrimaryAction != null) {
mPrimaryAction.setPrimaryAction(b);
}
}
这一步就是将RowBuilderImpl中fillFrom()的那些属性全部添加到了RowBuilderImpl的Slice.Builder中,而在Slice.builder中有一个items集合
所以RowBuiderImpl中的属性全部添加到了这个items集合中
然后调用Slice.Builder的build方法,把Items封装到一个Slice对象中,这样就创建出了一个Slice
最后把这个创建出来的Slice作为一个subSlice,封装成SliceItem添加到了ListBuider的SliceBuide的item中,至此addRow过程就完成了;
最后调用ListBuilder.build,操作跟上面步骤一样,最终创建了一个Slice,里面有一个Items集合,这个items集合中包含了很多SliceItem,每一个Slice模板,例如RowBuilder,HeaderBuilder相当于一个subSlice,而这些subSlice里面又包含了很多属性,将这些SubSlice最终添加到了这个items中,封装成了一个最终的Slice返回给了使用方;
而在使用方通过SliceLiveData.fromIntent(this, intent)方法底层会通过ContentProvider去发起绑定请求,并且把返回的Slice通过LiveData发送数据
绑定成功之后就会拿到提供方给的Slice,将接收到的slice设置给SliceView,在SliceView中会拿到对应Slice中item中对应的属性,然后绑定UI;