一、背景介绍:
3D Touch是一种立体触控技术,被苹果称为新一代多点触控技术,是在Apple Watch上采用的Force Touch,屏幕可感应不同的感压力度触控。3D Touch,苹果iPhone 6s的新功能,看起来类似 PC 上的右键。有Peek Pop 两种新手势。
随着IOS在iphone 6s 上加入了3d touch后,果粉手机很多应用都有了快捷访问的功能。
比如下图:
3D touch是需要硬件支持的,但是谷歌开放了系统,没有牢牢把控住硬件。就算谷歌实现了3Dtouch,其他安卓厂商估计也没这么快更新硬件。
但是我们也没有必要暗自菲薄不是,开放有开放的好处,封闭有封闭的缺点。
二、安卓实现:
好了,废话不多说。这篇文章的主题是介绍怎么在安卓上实现类似IOS 3d touch的功能。在安卓系统上,这个功能被称为应用程序快捷方式(shortcut),显示的入口是长按应用图标,也可以拖动快捷方式到桌面形成一个独立的入口。
首先,看看谷歌关于shortcut的介绍:
如果有梯子点这:谷歌关于shortcut介绍
应用程序快捷方式
如果您的应用定位到Android 7.1(API级别25)或更高级别,则可以定义 应用中特定操作的快捷方式。这些快捷方式可以显示在支持的启动器中。快捷方式可让您的用户快速启动应用内的常见或推荐任务。每个快捷方式都引用一个或多个 意图,当用户选择快捷方式时,每个意图都会在应用中启动特定操作。您可以表示为快捷方式的操作示例包括:
- 将用户导航到地图应用中的特定位置
- 在通信应用中向朋友发送消息
- 在媒体应用中播放电视节目的下一集
- 游戏在应用中加载求最后一个保存点
- 静态快捷方式在打包到APK或应用程序包中的资源文件中定义
- 只有在运行时,您的应用才能发布,更新和删除动态快捷方式
如果用户授予权限,则固定快捷方式可以在运行时固定到受支持的启动器。
注意:用户还可以通过将应用程序的静态和动态快捷方式复制到启动器上来创建固定快捷方式
您可以一次为应用程序发布最多五个快捷方式(静态快捷方式和动态快捷方式组合)。但是,某些启动器应用程序不会显示您为应用创建的所有静态和动态快捷方式。用户可以创建的应用固定快捷方式的数量没有限制。即使您的应用无法移除固定的快捷方式,它仍然可以禁用它们。
注意:虽然其他应用无法访问快捷方式中的元数据,但启动器本身可以访问此数据。因此,这些元数据应隐藏敏感的用户信息。
通过介绍我们知道:
- 有动态和静态两种注册快捷方式;
- 需要注意的是要保护用户的敏感信息,比如订单之类的信息;
- 可以动态发布,更新和删除;
- 可以固定它们到桌面,作为一种快捷方式的入口。
静态注册:
1. 首先在res下面新建目录xml,再新建shortcuts.xml文件, 路径:res/xml/shortcuts.xml
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="compose"
android:enabled="true"
android:icon="@drawable/compose_icon"
android:shortcutShortLabel="@string/compose_shortcut_short_label1"
android:shortcutLongLabel="@string/compose_shortcut_long_label1"
android:shortcutDisabledMessage="@string/compose_disabled_message1">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="com.example.myapplication"
android:targetClass="com.example.myapplication.ComposeActivity" />
<!-- If your shortcut is associated with multiple intents, include them
here. The last intent in the list determines what the user sees when
they launch this shortcut. -->
<categories android:name="android.shortcut.conversation" />
</shortcut>
<!-- Specify more shortcuts here. -->
</shortcuts>
2. 修改manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.william.shortcut">
<application ... >
<activity android:name="Main">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
</application>
</manifest>
注意:如果将多个意图与快捷方式关联,系统将启动与快捷方式在资源文件中的最后意图相对应的活动,以及后向堆栈中的其他活动 。在这种情况下,当用户选择快捷方式然后按后退键时,您的应用程序将启动与资源文件中列出的快捷方式倒数第二个意图相对应的活动。重复按下后退按钮后,此行为模式继续,直到用户清除快捷方式创建的后堆栈。当用户下一次按下后退按钮时,系统会将它们导航回启动器。
动态注册:
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "id1")
.setShortLabel("Web site")
.setLongLabel("Open the web site")
.setIcon(Icon.createWithResource(context, R.drawable.icon_website))
.setIntent(new Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.mysite.example.com/")))
.build();
shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
踪快捷方式用法:
要确定应出现静态和动态快捷方式的情况,启动器会检查快捷方式的激活历史记录。当发生以下任一事件时 reportShortcutUsed() ,您可以通过调用方法,传入快捷方式的ID来跟踪用户何时在应用内完成特定操作:用户选择具有给定ID的快捷方式。
用户打开应用程序并手动完成与同一快捷方式对应的操作。
禁用快捷方式:
由于您的应用及其用户可以将快捷方式固定到设备的启动器上,因此这些固定快捷方式可能会引导用户执行应用中已过期或不再存在的操作。要管理这种情况,您可以禁用不希望用户通过调用选择的disableShortcuts()快捷方式,这将从静态和动态快捷方式列表中删除指定的快捷方式,并禁用这些快捷方式的任何固定副本。您还可以使用此方法的 重载版本来定义在用户尝试启动已禁用的快捷方式时应显示的错误消息。
分配多个意图:
使用时创建快捷方式 ShortcutInfo.Builder,您可以使用 setIntents() 而不是 setIntent()。通过调用setIntents(),您可以在用户选择快捷方式时在应用程序中启动多个活动,将列表中的最后一个活动放在 后台堆栈中。如果用户随后决定按设备的后退按钮,他们将在您的应用中看到另一个活动,而不是返回设备的启动器。
最佳实践:
在设计和创建应用程序的快捷方式时,您应遵循以下准则:遵循快捷方式设计指南(快捷方式图标的设计规范)
没有梯子的点这里:点击打开链接
要使应用程序的快捷方式与系统应用程序使用的快捷方式在视觉上保持一致,请遵循应用程序快捷方式设计指南。
仅发布四个不同的快捷方式:
虽然API目前支持在任何给定时间为您的应用程序组合最多五个静态快捷方式和动态快捷方式,但建议您随时只发布四个不同的快捷方式,以改善启动器中快捷方式的可视外观。限制快捷方式描述长度:
菜单中的空间有限,可在启动器中显示应用程序的快捷方式。如果可能,将快捷方式的“简短描述”的长度限制为10个字符,并将“长描述”的长度限制为25个字符。
维护快捷方式和操作使用历史记录:
对于您创建的每个快捷方式,请考虑用户可以在应用程序中直接完成相同任务的不同方式。请记住reportShortcutUsed() 在每种情况下调用 ,以便启动器维护代表您的快捷方式的操作的准确历史记录。
仅在保留其含义时更新快捷方式:
更改动态和固定快捷方式时,updateShortcuts() 仅在更改保留其含义的快捷方式的信息时调用 。否则,您应该使用以下方法之一,具体取决于您要重新创建的快捷方式的类型:- 动态快捷方式: addDynamicShortcuts()或 setDynamicShortcuts()。
- 固定快捷方式: requestPinShortcut()。
例如,如果您创建了一个导航到超市的快捷方式,那么只要超市名称发生变化但其位置保持不变,只需更新快捷方式即可。但是,如果用户开始在不同的超市位置购物,则最好创建新的快捷方式。
备份和还原期间不会保留动态快捷方式:
当设备进行备份和还原操作时,不会保留动态快捷方式。因此,建议您检查getDynamicShortcuts() 每次启动应用程序时返回的对象数, 并根据需要重新发布动态快捷方式,如“ 备份和还原”部分中的代码段所示 。
下面我们用自己的代码实现动态注册吧:
首先,在真实的业务场景中,我们需要判断要跳转的目标页面是否需要登录,分别做处理
所以我在Demo中指定的跳转入口都是闪屏页
首先看看AndroidManifest.xml
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".SplashActivity"
android:theme="@style/theme_loading">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity
android:name=".TargetActivity"
android:screenOrientation="portrait" />
</application>
闪屏页代码:
/**
* 作者:William 时间:2018/7/14
* 类说明:闪屏页
*/
public class SplashActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String targetAction = getIntent().getStringExtra("targetAction");
new Handler().postDelayed(() -> {
// 真实的业务场景中,这里需要判断目标页面是否需要登录
// 这里简单模拟1秒钟后跳转到目标页面,统一在MainActivity做跳转
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("targetAction", targetAction);
startActivity(intent);
finish();// 避免回退时又能重新看到闪屏页
}, 1000);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private boolean exit;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createShortcut();
handleShortcutEvent();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handleShortcutEvent();
}
private void handleShortcutEvent() {
String targetAction = getIntent().getStringExtra("targetAction");
if (!TextUtils.isEmpty(targetAction)) {
TargetActivity.startTargetPage(this, targetAction);
}
}
/**
* 动态创建桌面图标长按菜单列表
*/
public void createShortcut() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
try {
boolean isShortcutCreated = PreferencesUtil.readBoolean("isShortcutCreated", false);
if (!isShortcutCreated) {// 如果有创建过就不再重复创建,可以考虑从接口获取
ShortcutManager mShortcutManager = getSystemService(ShortcutManager.class);
List<ShortcutInfo> infoList = new ArrayList<>();
infoList.add(createShortcutInfo(ShortcutUtil.ID1, "Action-1", 0,
R.mipmap.ic_launcher_round, "Action-1"));
infoList.add(createShortcutInfo(ShortcutUtil.ID2, "Action-2", 1,
R.mipmap.ic_launcher_round, "Action-2"));
infoList.add(createShortcutInfo(ShortcutUtil.ID3, "Action-3", 2,
R.mipmap.ic_launcher_round, "Action-3"));
infoList.add(createShortcutInfo(ShortcutUtil.ID4, "Action-4", 3,
R.mipmap.ic_launcher_round, "Action-4"));
if (mShortcutManager != null)
mShortcutManager.setDynamicShortcuts(infoList);
PreferencesUtil.writeBoolean("isShortcutCreated", true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ShortcutInfo createShortcutInfo(String id, String label, int rank, int iconResId, String targetAction) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
return new ShortcutInfo.Builder(this, id)
.setShortLabel("桌面" + label)// 设置桌面图标名称,可以拖动到桌面上的图标标题
.setLongLabel(label)// APP长按显示的标题
.setRank(rank)// 顺序显示,这个是相对的,它是始终按照离应用图标最近的顺序显示,如果图标在桌面最上和最下,显示的顺序是相反的
.setIcon(Icon.createWithResource(this, iconResId))// 快捷方式的图标
.setIntent(createIntent(targetAction))// 跳转的意图,官网推荐用setIntents()
// .setIntents(new Intent[]{})
.build();
}
return null;
}
private Intent createIntent(String targetAction) {
Intent intent = new Intent(this, SplashActivity.class);
intent.setAction(Intent.ACTION_MAIN);
intent.putExtra("targetAction", targetAction);
return intent;
}
@Override
public void onBackPressed() {
if (!exit) {
exit = true;
Toast.makeText(this, "再次点击退出", Toast.LENGTH_SHORT).show();
getWindow().getDecorView().postDelayed(() -> {
if (!isFinishing()) {
exit = false;
}
}, 1500);
} else {
Process.killProcess(Process.myPid());
System.exit(0);
}
}
}
TargetActivity.java:
/**
* 作者:William 时间:2018/7/14
* 类说明:目标测试页面
*/
public class TargetActivity extends AppCompatActivity {
public static void startTargetPage(Context context, String targetAction) {
Intent intent = new Intent(context, TargetActivity.class);
intent.putExtra("targetAction", targetAction);
context.startActivity(intent);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);
String targetAction = getIntent().getStringExtra("targetAction");
((TextView) findViewById(R.id.tv_action)).setText(targetAction);
String shortcutId = null;
if (targetAction != null) {
switch (targetAction) {
case "Action-1":
shortcutId = ShortcutUtil.ID1;
break;
case "Action-2":
shortcutId = ShortcutUtil.ID2;
break;
case "Action-3":
shortcutId = ShortcutUtil.ID3;
break;
case "Action-4":
shortcutId = ShortcutUtil.ID4;
break;
}
if (shortcutId != null) {
ShortcutUtil.report(this, shortcutId);
}
}
}
}
效果图1:
效果图2:
正如我们上面设置的rank顺序,但它是始终是按照离应用程序图标越近越先展示的,如下图:
到这里就基本实现安卓应用程序桌面快捷方式跳转功能了。
其实最优的做法是图标的信息都从接口获取,从而更加灵活的实现动态图标创建和更新。
当然如果跳转目标页面比较固定,可以选择不从接口获取,仁者见仁,智者见智!
说了这么多,但就目前经过测试,安卓手机厂商品牌中 oppo, vivo, 魅族,小米都不支持,目前已知只有华为和三星手机支持,至于其他的手机品牌,各位老铁可以自己试试,有行的记得在下面留言哦!!!
有关更多ShortcutManager的相关API点这里: 点击打开链接
Demo我已上传到github: 点击打开链接
欢迎大家留言与讨论!!!!!!