本文旨在通过重写GridView,配合系统弹窗实现仿今日头条的频道编辑页面
注:由于代码稍长,本文仅列出关键部分,完整工程请参见【https://github.com/G9YH/YHChannelEdit】
在开始讲解盗版的实现方案前,让我们先来看看正版与盗版的实际使用效果对比,首先是正版
接下来是盗版
当然,在部分视图的设计方面还是存在着不小的差异的,但这一页面大部分基本功能已然实现了。那么接下来,就让我们开始我们的模仿秀
实现思想
事实上,我的频道列表中,如何实现长按拖拽并交换频道位置是整个页面的核心难点。大致实现思路如下
- 长按某个频道后,在该频道上方生成一个与之相同的弹窗,同时隐藏该频道视图
- 当手指按下时,该弹窗跟随触摸点移动
- 弹窗移动过程中,根据触摸点交换其他频道位置
- 当手指抬起时,在触摸点当前对应的位置处生成一个与弹窗相同的频道视图
抛开这一问题,其余部分的实现逻辑都较为简单,这里不再赘述,下文将更会有具体实现的介绍
实现要点
我的频道
正如前文所言,这一部分的核心在于重写GridView以及系统弹窗,那么,首先自然是系统弹窗权限的开启
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
接下来即是GridView的重写。首先定义了两个常量用户标识当前的模式,即编辑模式和普通模式
private static final int MODE_EDIT = 1;
private static final int MODE_NORMAL = 2;
然后实现了OnItemLongClickListener接口
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
// 已处于移动模式
if (mode == MODE_EDIT) {
return false;
}
textEdit.setText("完成");
....
// 推荐标签无法移动或删除
if (i == 0) {
return false;
}
// 判断并获取弹窗权限
permissionGetter.alertWindowPermissionRequest();
....
// 初始化弹窗
initWindow();
return true;
}
这里需要注意到的是PermissionGetter类,我们知道,尽管在manifests中定义了系统弹窗的权限,但通常而言手机是需要用户手动为app开启相关权限的。PermissionGetter类的作用即在于此,该类通过分别处理小米、魅族以及华为等几个较为特殊的Android系统,基本实现了大部分机型的弹窗权限申请功能
/**
* 判断系统是否已为应用开启某项权限
*
* @param num 权限编号
* @return 已开启则返回0,否则返回1
*/
private int checkPermission(int num) {
int version = Build.VERSION.SDK_INT;
if (version >= 19) {
....
}
return -1;
}
....
/**
* Android 6.0之后的手机需要进行弹窗权限的申请
* 其中小米、魅族以及华为三种机型需要特殊处理
*/
public void alertWindowPermission() {
if (this.checkPermission(24) == 1) {
Toast toast = Toast.makeText(
context, "请先为您的手机开启悬浮窗权限", Toast.LENGTH_SHORT);
toast.show();
// 处理小米手机权限
if ("Xiaomi".equals(Build.MANUFACTURER)) {
....
}
}
// 处理魅族手机权限
else if ("Meizu".equals(Build.MANUFACTURER)) {
....
}
// 处理华为手机权限
else if ("Huawei".equals(Build.MANUFACTURER)) {
....
}
// 处理其他手机权限
else if (Build.VERSION.SDK_INT >= 23) {
....
}
}
}
在长按接口中实现了弹窗的初始化后,将模式mode设置为MODE_EDIT。此时即可通过重写onTouchEvent(MovtionEvent motionEvent)方法来判断何时进行弹窗的更新以及关闭等工作
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if (mode == MODE_EDIT) {
updateWindow(motionEvent);
}
break;
case MotionEvent.ACTION_UP:
if (mode == MODE_EDIT) {
closeWindow();
}
break;
}
return super.onTouchEvent(motionEvent);
}
当手指按下时,持续更新弹窗位置,并根据其位置交换其他频道的位置,当然不要忘记了交换动作相应的动画
当手指抬起时,将模式mode设置为MODE_NORMAL,并在弹窗当前对应的频道处生成一个与弹窗相同的视图,同时移除该弹窗视图即可
频道推荐
这一部分的实现就较为简单了,只需利用GridView展示频道,然后实现OnItemClickListener接口,点击时将该item移除并添加至我的频道视图中即可
@OnItemClick(R.id.grid_recommend) void gridRecommend(int position) {
String string = listHolder.getRecommendList().get(position);
// 我的频道中增加标签
listHolder.getMineList().add(string);
// 频道推荐中删除标签
listHolder.getRecommendList().remove(position);
// 更新各频道数据
mineAdapter.moveNotifyDataSetChanged(false, -1);
recommendAdapter.notifyDataSetChanged();
}
列表缓存
事实上,在实际开发中,通常可以采用SharedPreferences配合服务器端来实现我的频道以及频道推荐两个列表内容的持久化存储。但由于这里仅仅是实现一个demo,因此存储功能仅通过一个单例类ListHolder来模拟实现。其中ListHolder单例的实现方式如下,参考了我之前的一篇博客《单例模式的终极实现方案》
public class ListHolder {
private List<String> mineList = new ArrayList<>();
private List<String> recommendList = new ArrayList<>();
private static class Instance {
private static ListHolder instance = new ListHolder();
}
private ListHolder() {
}
public static ListHolder getInstance() {
return Instance.instance;
}
public get() & set()
}
优化改进
尽管到目前为止,我们已经实现了大部分的基本功能,但仍与正版有部分差异,例如频道列表内容的存储、部分动画的实现以及视图设计的差别等等,这一系列问题都将在之后的开发工作中继续优化
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。