Compare commits

..

1 Commits

Author SHA1 Message Date
khy
fc1dc858e2 修复BUG 2017-03-20 09:28:55 +08:00
4777 changed files with 85385 additions and 322783 deletions

17
.gitignore vendored
View File

@ -1,12 +1,9 @@
.idea/
/.idea
.idea/misc.xml
*.iml
.gradle/
local.properties
# sign.properties
.gradle
/local.properties
.DS_Store
captures/
build/
release-app/
test-app/
scripts/apk-channel/
app/src/test/java/com/gh/gamecenter
/build
/captures
/PushSDK/build

View File

@ -1,44 +0,0 @@
stages:
- analysis
- sendmail
## 代码检查
sonarqube_analysis:
tags:
- offline-test
stage: analysis
image: sonarsource/sonar-scanner-cli:latest
dependencies: [] #禁止传递来的artifact
script:
## 获取项目的一级组和二级组和项目名作为projectKey例如projectKey=platform-backend-eci-monitor
- group=`echo $CI_PROJECT_PATH | sed 's#/#-#g'`
- sonar-scanner
-Dsonar.host.url=http://sonarqube-server.sonarqube:9000/
-Dsonar.login=be43de7264ce4c4766eb0c020373c3e74e6df257
-Dsonar.jacoco.reportPaths=target/jacoco.exec
-Dsonar.projectKey=$group
-Dsonar.projectName=$CI_PROJECT_PATH
-Dsonar.sourceEncoding=UTF-8
-Dsonar.exclusions=**/vendor/**,**/errcode/**
-Dsonar.gitlab.project_id=$CI_PROJECT_ID
-Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA
-Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME
-Dsonar.gitlab.ci_merge_request_iid=$CI_MERGE_REQUEST_IID
-Dsonar.gitlab.merge_request_discussion=true
-Dsonar.java.binaries=. # 如果不使用Maven或Gradle进行分析则必须手动提供测试二进制文件
only:
- dev
## 发送简易检测结果报告
send_sonar_report:
tags:
- offline-test
stage: sendmail
image: hub.shanqu.cc/library/docker:latest
dependencies: [] #禁止传递来的artifact
script:
- group=`echo $CI_PROJECT_PATH | sed 's#/#-#g'`
- docker login -u "${HARBOR_REGISTRY_USERNAME}" -p "${HARBOR_REGISTRY_PASSWORD}" "${HARBOR_REGISTRY}"
- docker run -e PROJECTKEY=$group -e EMAIL=$GITLAB_USER_EMAIL --name send-email --rm hub.shanqu.cc/platform/send-sonar-report:latest
only:
- dev

7
.gitmodules vendored
View File

@ -1,7 +0,0 @@
[submodule "libraries/LGLibrary"]
path = libraries/LGLibrary
url = git@git.shanqu.cc:android/common-library.git
branch = master
[submodule "assistant_flutter"]
path = assistant_flutter
url = git@git.shanqu.cc:halo/android/flutter-module.git

View File

@ -1,89 +0,0 @@
# 版本升级备忘
### Ver 2.5
* 此处写本次更新所做的业务和代码修改
### Ver 2.6
* xx
### Ver 3.0
* 升级账号系统(登录流程/用户信息相关/用户账号相关操作(评论,礼包...))
* 新增收藏功能(文章/工具箱)
* 删除用户相关的所有本地数据库
* 重做总开服表
* 重做首页插件化模块
* 礼包重复领取机制改变(可重复领取的礼包,领取后立刻显示再领一个/再淘一个)
* 游戏下载平台面板修改(加快弹出速度,不再读取本地平台图片)
* 接入bugly(tinker)
### VER 3.1
### VER 3.2
### VER 3.3
### VER 3.4
### VER 3.5
### Ver 3.6
* 首页游戏增加预览骨架,游戏ITEM样式微调和开服标签
* 首页问答增加关注页面
* 重构游戏更新管理(游戏更新/插件化/已安装的游戏),具体细节参考PackageRepository & PackageViewModel
* 重构APP更新管理(已VersionVode为更新基准,小版本更新改为光环后台控制)
- 删除TINKER_VERISON_NAME
- tinker打包方式变更(以小版本作为Base包,防止与数据后台小版本更新发生冲突)
* 社区增加版主功能(版主可以对存在的相关内容进行修改/隐藏操作,内容包括问题/回答/回答评论)
* 社区互动引导优化(问答推荐增加`推荐关注`,回答详情增加一些交互动效)
### Ver 3.6.1
* 可以后台控制关闭资讯功能
* 版块、分类、专题详情、游戏详情、礼包详情增加预览骨架
* 下载按钮状态可以通过接口屏蔽相应的包
### Ver 3.6.2
* 资讯/问答入口和插件功能线上控制(不可逆)
* 首页不显示已安装的游戏
* 插件求版本功能增加内部跳转
* 下载面板增加公告和版本说明功能
* 接入腾讯`广点通`(广告)
### ver 3.6.3
* 社区搜索修改
- 增加 `文章/用户` 模块
- 增加 `搜索置顶` 功能
* 回答详情/社区文章详情修改
- 支持文案样式(加粗/斜体/删除线)和段落样式(引用/标题)
- 支持关闭评论功能
- 回答详情新增上下切换回答
* 社区编辑框(回答/文章)修改
- 支持批量插入图片(使用知乎Matisse实现)
- 新增插入特殊样式,文案样式(加粗/斜体/删除线)和段落样式(引用/标题)
* 编辑框部分 JS/CSS 使用远程文件
### ver 3.6.4
* 增加浏览记录(回答/文章/资讯/游戏)
* 回答/社区文章 增加反对功能
* 社区编辑框增加插入文章/回答/游戏
- 低版本兼容方案: 插入的样式默认隐藏,只有在3.6.4及以上才会显示
* 游戏详情评分模块增加`小编评论`区域以及样式修改
* 游戏评分增加回复功能
### var 3.6.5
* 以补丁方式向外推出并没有增加需求只是单纯的修BUG
### var 3.6.6
* 游戏详情:
- 支持修改评分
- 评分列表增加排序/过滤功能
- 增加弹出系统
* 社区相关:
- 选择社区页面重做
- 首页社区推荐增加推荐入口
- 首页社区问题模块改版,名称改为全部,去除问题分类(统一为问题列表),增加社区文章列表
* 游戏搜索默认页面改版
* 权限系统更改,不授权也可以进去App,申请权限细分到功能(用到某个功能时才需要强制授予权限)
* 增加隐私系统
* 增加游戏预约功能
* 增加标签详情模块
* 进入今日头条广告SDK
* 图片上传压缩机制优化
- 支持从后台修改上传配置(本该在早些版本实现,由于代码问题无法引用后台配置)
- 对压缩失败是进行catch(由于后台对宽高配置放宽,很容易发生OOM),失败后直接上传原图(这步可能会出现问题)
* 游戏相关UI修改

147
PushSDK/AndroidManifest.xml Normal file
View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.umeng.message.lib"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_CHANGED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_INSTALL" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REPLACED" />
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<application>
<service
android:name="com.taobao.accs.ChannelService"
android:exported="true"
android:process=":channel">
<intent-filter>
<action android:name="com.taobao.accs.intent.action.SERVICE" />
</intent-filter>
<intent-filter>
<action android:name="com.taobao.accs.intent.action.ELECTION" />
</intent-filter>
</service>
<service
android:name="com.taobao.accs.data.MsgDistributeService"
android:exported="true">
<intent-filter>
<action android:name="com.taobao.accs.intent.action.RECEIVE" />
</intent-filter>
</service>
<receiver
android:name="com.taobao.accs.EventReceiver"
android:process=":channel">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver
android:name="com.taobao.accs.ServiceReceiver"
android:exported="false"
android:process=":channel">
<intent-filter>
<action android:name="com.taobao.accs.intent.action.COMMAND" />
</intent-filter>
<intent-filter>
<action android:name="com.taobao.accs.intent.action.START_FROM_AGOO" />
</intent-filter>
</receiver>
<service
android:name="com.taobao.accs.ChannelService$KernelService"
android:process=":channel">
</service>
<service
android:name="org.android.agoo.accs.AgooService"
android:exported="true">
<intent-filter>
<action android:name="com.taobao.accs.intent.action.RECEIVE" />
</intent-filter>
</service>
<service android:name="com.umeng.message.UmengIntentService"
android:exported="true"
android:process=":channel">
<intent-filter>
<action android:name="org.agoo.android.intent.action.RECEIVE" />
</intent-filter>
</service>
<receiver
android:name="com.taobao.agoo.AgooCommondReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.gh.gamecenter.intent.action.COMMAND" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver
android:name="com.umeng.message.NotificationProxyBroadcastReceiver"
android:exported="false" />
<service
android:name="com.umeng.message.UmengMessageCallbackHandlerService"
android:exported="false">
<intent-filter>
<action android:name="com.umeng.messge.registercallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.enablecallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.disablecallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.message.handler.action" />
</intent-filter>
</service>
<service
android:name="com.umeng.message.UmengDownloadResourceService"
android:exported="false" />
<service
android:name="com.umeng.message.UmengMessageIntentReceiverService"
android:exported="true"
android:process=":channel" >
<intent-filter>
<action android:name="org.android.agoo.client.MessageReceiverService" />
</intent-filter>
</service>
<provider
android:name="com.umeng.message.provider.MessageProvider"
android:authorities="com.gh.gamecenter.umeng.message"
android:exported="false">
<grant-uri-permission android:pathPattern=".*" />
</provider>
</application>
</manifest>

51
PushSDK/build.gradle Normal file
View File

@ -0,0 +1,51 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 22
buildToolsVersion "23.0.2"
defaultConfig {
minSdkVersion 11
targetSdkVersion 22
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.umeng.message.lib.test" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="22" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:functionalTest="false"
android:handleProfiling="false"
android:label="Tests for com.umeng.message.lib.test"
android:targetPackage="com.umeng.message.lib.test" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_CHANGED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_INSTALL" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REPLACED" />
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application>
<uses-library android:name="android.test.runner" />
<service
android:name="com.taobao.accs.ChannelService"
android:exported="true"
android:process=":channel" >
<intent-filter>
<action android:name="com.taobao.accs.intent.action.SERVICE" />
</intent-filter>
<intent-filter>
<action android:name="com.taobao.accs.intent.action.ELECTION" />
</intent-filter>
</service>
<service
android:name="com.taobao.accs.data.MsgDistributeService"
android:exported="true" >
<intent-filter>
<action android:name="com.taobao.accs.intent.action.RECEIVE" />
</intent-filter>
</service>
<receiver
android:name="com.taobao.accs.EventReceiver"
android:process=":channel" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver
android:name="com.taobao.accs.ServiceReceiver"
android:exported="false"
android:process=":channel" >
<intent-filter>
<action android:name="com.taobao.accs.intent.action.COMMAND" />
</intent-filter>
<intent-filter>
<action android:name="com.taobao.accs.intent.action.START_FROM_AGOO" />
</intent-filter>
</receiver>
<service
android:name="com.taobao.accs.ChannelService$KernelService"
android:process=":channel" >
</service>
<service
android:name="org.android.agoo.accs.AgooService"
android:exported="true" >
<intent-filter>
<action android:name="com.taobao.accs.intent.action.RECEIVE" />
</intent-filter>
</service>
<service
android:name="com.umeng.message.UmengIntentService"
android:exported="true"
android:process=":channel" >
<intent-filter>
<action android:name="org.agoo.android.intent.action.RECEIVE" />
</intent-filter>
</service>
<receiver
android:name="com.taobao.agoo.AgooCommondReceiver"
android:exported="true" >
<intent-filter>
<action android:name="com.gh.gamecenter.intent.action.COMMAND" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver
android:name="com.umeng.message.NotificationProxyBroadcastReceiver"
android:exported="false" />
<service
android:name="com.umeng.message.UmengMessageCallbackHandlerService"
android:exported="false" >
<intent-filter>
<action android:name="com.umeng.messge.registercallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.enablecallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.disablecallback.action" />
</intent-filter>
<intent-filter>
<action android:name="com.umeng.message.message.handler.action" />
</intent-filter>
</service>
<service
android:name="com.umeng.message.UmengDownloadResourceService"
android:exported="false" />
<service
android:name="com.umeng.message.UmengMessageIntentReceiverService"
android:exported="true"
android:process=":channel" >
<intent-filter>
<action android:name="org.android.agoo.client.MessageReceiverService" />
</intent-filter>
</service>
<provider
android:name="com.umeng.message.provider.MessageProvider"
android:authorities="com.gh.gamecenter.umeng.message"
android:exported="false" >
<grant-uri-permission android:pathPattern=".*" />
</provider>
</application>
</manifest>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,12 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-19
android.library=true

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -1,65 +0,0 @@
# 光环助手Android客户端
### 概述
光环助手Android客户端目前使用 Kotlin 作为主要开发语言,以 MVVM 作为参考架构模式进行开发
### 约束
为编写易读易维护且较健壮的代码,可参考以下约束
1. 尽量将逻辑代码放置于 ViewModel 中View 中只执行 UI 操作
2. 尽量使 View 在被销毁之后仍能恢复状态,处理方式可参考 [保存界面状态](https://developer.android.com/topic/libraries/architecture/saving-states)
3. 尽量参考原有文件结构及命名规范,即以 大模块 - 小模块 的形式生成包关系
4. 遵循最小改动原则,在提交代码前务必先检查变动的代码,尽量以可控的变动规模来构成一个 commit ,以便日后追踪问题
5. 代码规范可参考 [AOSP Java 风格](https://source.android.com/setup/contribute/code-style)
6. 尽量使用 Kotlin 来写新文件
7. 尽量不要使用 DataBinding
8. Commit 前请确保不带入非项目必须文件,可手动修改 [.gitignore](https://stackoverflow.com/questions/8527597/how-do-i-ignore-files-in-a-directory-in-git) 文件忽略
9. 优先使用 ViewBinding 获取 View 对象
10. No AsyncTask!
### 公用部分
本项目使用 LiveData 实现了一个简单通用的基础列表分页功能,具体可见 `ListFragment`, `ListViewModel` 等类,理想情况下只需少量代码即可新建一个简单分页列表
### 首次拉取项目代码
`git clone -b dev git@git.shanqu.cc:halo/android/assistant-android.git --recursive`
### git 版本管理
本项目使用简化版的 git flow 来管理分支,细节请看 [光环安卓简单 git 规范](https://git.ghzs.com/halo/android/assistant-android/-/wikis/%E5%85%89%E7%8E%AF%E5%AE%89%E5%8D%93%E7%AE%80%E5%8D%95-git-%E8%A7%84%E8%8C%83)
### API 环境配置
本项目使用 Build Variants 来切换 API 环境
* internal 为测试环境
* publish 为正式环境
### 图片资源配置
* 新增图片资源时,默认只添加最高规格的 xxxhdpi 文件
* 新增图片资源时,需要将其转换为 .webp 格式 (包括含透明图层的图片默认质量为90%) (转换后体积变大的文件除外)
### 第三方appkey等配置
* 修改 `gradle.properties` 文件将各种key填入其中实现统一管理
* 通过 gradle 文件内的 resValue/buildConfigField/manifestPlaceHolder 方式实现编译期间修改,具体情况请参考 ``./build.gradle`` 和 ``./app/build.gradle`` 配置
### 混淆配置
* 本项目使用了微信的 [AndResGuard](https://github.com/shwenzhang/AndResGuard) 作为资源混淆压缩方案,新增需要使用 `getIdentifier` 获取的资源文件时需要添加至白名单
* 本项目默认使用 R8 作为混淆工具,往 proguard-rules.txt 添加 proguard 新配置项时请检查可用性(如语法等)
### APK打包配置
> 打内部测试包:`./scripts/test_build.sh`
> 打邮件测试包:`./scripts/jenkins_build.sh`
### TODO
* 把原有 EventBus 的消息 Type 统一到一个文件内
* 将实现细节从 View(Fragment、Activity) 剥离并以 MVVM 结构改造
* 重构 MainActivity

View File

@ -1,598 +1,153 @@
// This comment exists for a reason, do not delete
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' // kotlin
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'AndResGuard'
import groovy.xml.XmlUtil
android {
buildFeatures {
viewBinding true
dataBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
compileSdkVersion 21
buildToolsVersion "23.0.3"
dexOptions {
// jumboMode = true
javaMaxHeapSize "4g"
preDexLibraries true
maxProcessCount 8
jumboMode = true
}
aaptOptions {
additionalParameters "--no-version-vectors"
}
kapt {
useBuildCache = true
javacOptions {
// 增加注解处理器的最大错误次数,默认为 100
option("-Xmaxerrs", 500)
}
}
/**
* 定位编译出错的图片
*/
aaptOptions.cruncherEnabled = false
aaptOptions.useNewCruncher = false
defaultConfig {
applicationId "com.gh.gamecenter"
minSdkVersion 14
targetSdkVersion 21
versionCode 19
versionName "2.4"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex: 'com.gh.EventBusIndex']
}
}
ndk {
// 如果不添加 `arm64` 调用系统的 PackageManager 的方法读取安装包信息的时候会出现 native 层闪退,草
// 添加了 `arm64` 以后部分 5.0 的设备会报用错 so 的问题,
// couldn't find DSO to load: libimagepipeline.so caused by: dlopen failed: "/data/data/com.gh.gamecenter/lib-main/libimagepipeline.so" is 64-bit instead of 32-bit result: 0
// 以 OPPO R7PLUS 为例,明明设备是骁龙 615ARMv8-64 bit 的设备却不支持 arm64 的 abi限制了只使用 java 后还是报错,只有 5.05.1 设备无法复现 : (
// 惊了
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
}
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
// 由于app只针对中文用户所以仅保留zh资源其他删掉
resConfigs "zh"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
applicationId rootProject.ext.applicationId
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-fresco.txt'
/**
* All third-party appid/appkey
*/
buildConfigField "String", "API_HOST", "\"${API_HOST}\""
buildConfigField "String", "NEW_API_HOST", "\"${NEW_API_HOST}\""
buildConfigField "String", "WECHAT_APPID", "\"${WECHAT_APPID}\""
buildConfigField "String", "WECHAT_SECRET", "\"${WECHAT_SECRET}\""
buildConfigField "String", "TENCENT_APPID", "\"${TENCENT_APPID}\""
buildConfigField "String", "WEIBO_APPKEY", "\"${WEIBO_APPKEY}\""
buildConfigField "String", "QUICK_LOGIN_APPID", "\"${QUICK_LOGIN_APPID}\""
buildConfigField "String", "QUICK_LOGIN_APPKEY", "\"${QUICK_LOGIN_APPKEY}\""
/**
* Build Time 供区分 jenkins 打包时间用
* IS_NIGHT_MODE_ON 供区分夜间模式功能是否启用
*/
buildConfigField "long", "BUILD_TIME", "0"
buildConfigField "boolean", "IS_NIGHT_MODE_ON", "true"
// multiDexEnabled true
}
// gradle 2.2以上默认同时启用v1和v2优先用于Android N
/**
* 签名设置
*/
signingConfigs {
debug {
v1SigningEnabled true
v2SigningEnabled true
}
release {
v1SigningEnabled true
v2SigningEnabled true
storeFile file("gh.keystore")
keyAlias "gh.keystore"
keyPassword "20150318"
storePassword "20150318"
}
}
buildTypes {
debug {
debuggable true
minifyEnabled false
zipAlignEnabled false
signingConfig signingConfigs.debug
buildConfigField "String", "EXPOSURE_REPO", "\"test\""
buildConfigField "String", "EXPOSURE_VERSION", "\"E4\""
multiDexKeepProguard file("tinker_multidexkeep.pro")
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
debuggable false
minifyEnabled true
zipAlignEnabled true
shrinkResources true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
buildConfigField "String", "EXPOSURE_REPO", "\"exposure\""
buildConfigField "String", "EXPOSURE_VERSION", "\"E4\""
multiDexKeepProguard file("tinker_multidexkeep.pro")
}
}
// Ignore useless variant
variantFilter { variant ->
def names = variant.flavors*.name
def isDebugType = variant.buildType.name == "debug"
if ((names.contains("tea")) && isDebugType) {
setIgnore(true)
}
}
flavorDimensions("env")
sourceSets {
publish {
java.srcDirs = ['src/main/java']
}
internal {
java.srcDirs = ['src/main/java']
}
tea {
java.srcDirs = ['src/main/java', 'src/tea/java']
}
}
// sourceSets {
// main {
// jniLibs.srcDirs = ['libs']
// }
// }
/**
* 多渠道打包
*/
productFlavors {
// internal test dev host
internal {
dimension "env"
versionNameSuffix "-debug"
buildConfigField "String", "DEV_API_HOST", "\"${DEV_API_HOST}\""
buildConfigField "String", "NEW_DEV_API_HOST", "\"${NEW_DEV_API_HOST}\""
}
// publish release host˛
publish {
dimension "env"
buildConfigField "String", "DEV_API_HOST", "\"${API_HOST}\""
buildConfigField "String", "NEW_DEV_API_HOST", "\"${NEW_API_HOST}\""
}
tea {
dimension "env"
buildConfigField "String", "DEV_API_HOST", "\"${API_HOST}\""
buildConfigField "String", "NEW_DEV_API_HOST", "\"${NEW_API_HOST}\""
manifestPlaceholders.put("APPLOG_SCHEME", "rangersapplog.byAx6uYt".toLowerCase())
}
GH_100 {}
GH_101 {}
GH_102 {}
GH_103 {}
GH_104 {}
GH_106 {}
GH_107 {}
GH_108 {}
GH_109 {}
GH_110 {}
GH_111 {}
GH_113 {}
GH_114 {}
GH_115 {}
GH_116 {}
GH_117 {}
GH_118 {}
GH_119 {}
GH_120 {}
GH_121 {}
GH_123 {}
GH_127 {}
GH_200 {}
GH_201 {}
GH_202 {}
GH_203 {}
GH_204 {}
GH_205 {}
GH_222 {}
GH_307 {}
GH_TEST {}
}
lintOptions {
// For flutter release build, see https://github.com/flutter/flutter/issues/58247
checkReleaseBuilds false
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [CHANNEL_VALUE: name]//命令 gradlew assembleRelease
}
}
repositories {
flatDir {
dirs 'libs', 'libs/aars'
//butterknife
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
testImplementation 'junit:junit:4.12'
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakcanary}"
debugImplementation "com.squareup.okhttp3:logging-interceptor:${okHttp}"
// debugImplementation "com.gu.android:toolargetool:${toolargetool}" // 需要使用调试时才启用
debugImplementation "com.github.nichbar:WhatTheStack:${whatTheStack}"
debugImplementation "io.github.didi.dokit:dokitx:${dokit}"
implementation "androidx.core:core-ktx:${core}"
implementation "androidx.fragment:fragment-ktx:${fragment}"
implementation "androidx.multidex:multidex:${multiDex}"
implementation "androidx.appcompat:appcompat:${appCompat}"
implementation "androidx.cardview:cardview:${cardView}"
implementation "androidx.annotation:annotation:${annotation}"
implementation "androidx.constraintlayout:constraintlayout:${constraintLayout}"
implementation "androidx.recyclerview:recyclerview:${recyclerView}"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifeCycle"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifeCycle"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifeCycle"
implementation "androidx.lifecycle:lifecycle-extensions:$lifeCycleExtensions"
implementation "androidx.room:room-runtime:${room}"
implementation "androidx.room:room-rxjava2:${room}"
implementation "androidx.core:core-ktx:${ktx}"
implementation "androidx.viewpager2:viewpager2:${viewpager2}"
implementation "androidx.webkit:webkit:${webkit}"
kapt "androidx.room:room-compiler:${room}"
implementation "com.google.android.material:material:${material}"
implementation "com.kyleduo.switchbutton:library:${switchButton}"
implementation "com.facebook.fresco:fresco:${fresco}"
implementation "com.facebook.fresco:animated-gif-lite:${fresco}"
implementation "com.facebook.fresco:animated-drawable:${fresco}"
implementation "com.facebook.fresco:animated-webp:${fresco}"
implementation "com.facebook.fresco:webpsupport:${fresco}"
implementation "com.squareup.okhttp3:okhttp:${okHttp}"
implementation "com.leon.channel:helper:${apkChannelPackage}"
implementation "com.squareup.retrofit2:retrofit:${retrofit}"
implementation "com.squareup.retrofit2:converter-gson:${retrofit}" // include gson 2.7
implementation "com.squareup.retrofit2:adapter-rxjava2:${retrofit}"
implementation "com.j256.ormlite:ormlite-android:${ormlite}"
implementation "com.j256.ormlite:ormlite-core:${ormlite}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
implementation "org.greenrobot:eventbus:${eventbus}"
kapt "org.greenrobot:eventbus-annotation-processor:${eventbusApt}"
implementation "io.reactivex.rxjava2:rxjava:${rxJava2}"
implementation "io.reactivex.rxjava2:rxandroid:${rxAndroid2}"
implementation "com.jakewharton.rxbinding2:rxbinding:${rxBinding2}"
implementation "com.google.zxing:core:${zxing}"
implementation "com.google.zxing:android-core:${zxing}"
implementation "com.daimajia.swipelayout:library:${swipeLayout}"
implementation "com.google.android:flexbox:${flexbox}"
implementation "pub.devrel:easypermissions:${easypermissions}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.contrarywind:Android-PickerView:${pickerView}"
implementation "com.scwang.smartrefresh:SmartRefreshLayout:${smartRefreshLayout}"
implementation "net.cachapa.expandablelayout:expandablelayout:${expandableLayout}"
// 用于比较 versionName 是大于小于或等于
implementation "com.g00fy2:versioncompare:${versioncompare}"
implementation "top.zibin:Luban:${luban}"
implementation "com.squareup.picasso:picasso:${picasso}"
// for video streaming
implementation("com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-java:$gsyVideo", {
exclude module: "gsyvideoplayer-androidvideocache"
exclude group: "tv.danmaku.ijk.media"
})
implementation "com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-exo_player2:$gsyVideo"
implementation "androidx.work:work-runtime:${workManager}"
implementation "com.llew.huawei:verifier:${verifier}"
implementation "com.github.tbruyelle:rxpermissions:${rxPermissions}"
implementation "com.lg:skeleton:${skeleton}"
implementation "com.tencent.mm.opensdk:wechat-sdk-android-without-mta:${mta}"
implementation "com.github.nichbar:AndroidRomChecker:${romChecker}"
debugImplementation "com.github.nichbar.chucker:library:${chucker}"
releaseImplementation "com.github.nichbar.chucker:library-no-op:${chucker}"
teaImplementation "com.bytedance.applog:RangersAppLog-Lite-cn:${bytedanceApplog}"
implementation "com.aliyun.dpa:oss-android-sdk:${oss}"
implementation "com.airbnb.android:lottie:${lottie}"
implementation "net.lingala.zip4j:zip4j:${zip4j}"
implementation "io.sentry:sentry-android:4.3.0"
implementation("com.github.piasy:BigImageViewer:${bigImageViewer}", {
exclude group: 'com.squareup.okhttp3'
exclude group: 'androidx.swiperefreshlayout'
exclude group: 'com.github.bumptech.glide'
exclude group: 'com.facebook.fresco'
})
implementation "com.github.PhilJay:MPAndroidChart:${chart}"
implementation "com.lahm.library:easy-protector-release:${easyProtector}"
implementation "com.github.hsiafan:apk-parser:${apkParser}"
implementation "org.nanohttpd:nanohttpd:${nanohttpd}"
implementation "com.aliyun.openservices:aliyun-log-android-sdk:${aliyunLog}"
implementation "com.lg:easyfloat:${easyFloat}"
implementation "io.github.florent37:shapeofview:${shapeOfView}"
implementation "io.github.sinaweibosdk:core:${weiboSDK}"
implementation "com.lg:apksig:${apksig}"
implementation "com.lg:gid:${gid}"
implementation "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl:${splitties}"
compileOnly "com.github.axen1314.lancet:lancet-base:${lancet_version}"
implementation project(':libraries:LGLibrary')
implementation project(':libraries:QQShare')
implementation project(':libraries:Matisse')
}
File propFile = file('sign.properties')
if (propFile.exists()) {
Properties props = new Properties()
props.load(new FileInputStream(propFile))
if (props.containsKey('keyAlias') && props.containsKey('keyPassword') &&
props.containsKey('storeFile') && props.containsKey('storePassword')) {
android.signingConfigs {
// debug 不要使用正式签名这样tinker才不会打补丁。
debug {
keyAlias props.get('keyAlias')
keyPassword props.get('keyPassword')
storeFile file(props.get('storeFile'))
storePassword props.get('storePassword')
}
release {
keyAlias props.get('keyAlias')
keyPassword props.get('keyPassword')
storeFile file(props.get('storeFile'))
storePassword props.get('storePassword')
}
}
} else {
android.buildTypes.release.signingConfig = null
}
} else {
android.buildTypes.release.signingConfig = null
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:21.0.0'
// compile 'com.android.support:cardview-v7:21.0.0'
// fresco图片框架
compile 'com.facebook.fresco:fresco:0.12.0'
compile 'com.facebook.fresco:animated-gif:0.12.0'
// Retrofit2所需要的包
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
// okhttp
compile 'com.squareup.okhttp3:okhttp:3.2.0'
// ConverterFactory的Gson依赖包
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
// ConverterFactory的String依赖包
// compile 'com.squareup.retrofit2:converter-scalars:2.0.0-beta4'
// ConverterFactory的RxJava依赖包
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'
// gson
compile 'com.google.code.gson:gson:2.8.0'
// OrmLite数据库
compile 'com.j256.ormlite:ormlite-android:5.0'
compile 'com.j256.ormlite:ormlite-core:5.0'
// butterknife
compile 'com.jakewharton:butterknife:8.4.0'
apt 'com.jakewharton:butterknife-compiler:8.4.0'
// RxJava && RxAndroid
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
// RxBinding
compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'
// compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0'
// compile 'com.jakewharton.rxbinding:rxbinding-design:0.3.0'
//添加友盟依赖工程
compile project(':PushSDK')
// zxing 二维码扫描以及生成
compile 'com.google.zxing:core:3.2.1'
compile 'com.google.zxing:android-core:3.2.1'
//tinker
// compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
// compile "com.android.support:multidex:1.0.1"
}
// 用于测试读取 META-INF 里的文件
//task generateMetaJson {
// def resDir = new File(buildDir, 'generated/FILES_FOR_META_INF/')
// def destDir = new File(resDir, 'META-INF/')
// // Add resDir as a resource directory so that it is automatically included in the APK.
// android {
// sourceSets {
// main.resources {
// srcDir resDir
// }
// }
// }
//
// doLast {
// if (!destDir.exists()) destDir.mkdirs()
// copy {
// into destDir
// from new File('generated/FILES_FOR_META_INF/META-INF/halo_skip.json')
// }
// }
//}
//// Specify when put_files_in_META_INF should run
//project.afterEvaluate {
// tasks.findAll { task ->
// task.name.startsWith('merge') && task.name.endsWith('Resources')
// }.each { t -> t.dependsOn generateMetaJson }
//}
andResGuard {
mappingFile = null
use7zip = true
useSign = true
// 打开这个开关会keep住所有资源的原始路径只混淆资源的名字
keepRoot = false
// 设置这个值会把arsc name列混淆成相同的名字减少string常量池的大小
fixedResName = "arg"
// 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源
mergeDuplicatedRes = true
whiteList = [
"R.drawable.icon",
"R.drawable.ic_bar_back",
"R.drawable.toolbar_search_icon",
"R.drawable.bg_notification_answer_style_1",
"R.drawable.bg_notification_answer_style_2",
"R.drawable.bg_notification_article_style_1",
"R.drawable.bg_notification_article_style_2",
"R.drawable.bg_notification_feedback_style_1",
"R.drawable.bg_notification_feedback_style_2",
"R.drawable.bg_notification_gift_style_1",
"R.drawable.bg_notification_gift_style_2",
"R.drawable.bg_notification_login_style_1",
"R.drawable.bg_notification_login_style_2",
"R.drawable.bg_notification_question_style_1",
"R.drawable.bg_notification_question_style_2",
"R.drawable.bg_notification_rating_style_1",
"R.drawable.bg_notification_rating_style_2",
"R.drawable.bg_notification_reserve_game_style_1",
"R.drawable.bg_notification_reserve_game_style_2",
"R.drawable.bg_notification_video_style_1",
"R.drawable.bg_notification_video_style_2",
"R.drawable.ic_search_no_1",
"R.drawable.ic_search_no_2",
"R.drawable.ic_search_no_3",
"R.drawable.ic_search_no_4",
"R.drawable.ic_search_no_5",
"R.drawable.ic_search_no_6",
"R.drawable.ic_search_no_7",
"R.drawable.ic_search_no_8",
"R.drawable.ic_search_no_9",
"R.drawable.ic_search_no_10",
"R.drawable.ic_search_no_11",
"R.drawable.ic_search_no_12",
"R.drawable.ic_search_no_13",
"R.drawable.ic_search_no_14",
"R.drawable.ic_search_no_15",
"R.drawable.ic_search_no_16",
"R.drawable.ic_search_no_17",
"R.drawable.ic_search_no_18",
"R.drawable.ic_search_no_19",
"R.drawable.ic_search_no_20",
"R.drawable.ic_recommend_activity",
"R.drawable.ic_recommend_discount",
"R.drawable.ic_recommend_function",
"R.drawable.ic_recommend_gift",
"R.drawable.ic_recommend_role",
"R.drawable.login_btn_bg",
"R.drawable.ic_quick_login_check",
"R.drawable.ic_quick_login_uncheck",
"R.anim.anim_auth_in",
"R.anim.anim_auth_out",
"R.id.download_speed",
"R.id.download_percentage",
"R.id.comment",
"R.id.vote",
"R.id.watermark_hint",
"R.id.watermark_sb",
"R.id.bottomShareIv",
"R.id.bottomShareTv",
"R.id.recommendStarPref",
"R.id.recommendStar",
"R.drawable.help_search_delete",
"R.drawable.suggest_type_normal",
"R.drawable.suggest_type_crash",
"R.drawable.suggest_type_game_question",
"R.drawable.suggest_type_game_collect",
"R.drawable.suggest_type_function_suggest",
"R.drawable.suggest_type_article_collect",
"R.drawable.suggest_type_copyright",
"R.drawable.help_result_empty",
"R.drawable.news_comment_detail_read",
"R.drawable.news_comment_detail_comment",
"R.drawable.news_comment_detail_share",
"R.drawable.ic_libao",
"R.drawable.ic_link",
"R.drawable.concern_message_icon",
"R.drawable.reuse_blank_hint",
"R.drawable.ic_concern",
"R.drawable.concern_down",
"R.drawable.concern_up",
"R.drawable.ic_libao_more",
"R.drawable.ic_libao_delete",
"R.drawable.ic_dialog_close",
"R.drawable.occupy2",
"R.drawable.kc_checkbox_unselect",
"R.drawable.kc_checkbox_select",
"R.drawable.ic_type_unselect",
"R.drawable.ic_type_selected",
"R.drawable.suggest_add_pic_icon",
"R.drawable.icon_pic_add",
"R.drawable.ask_search_input_delete",
"R.drawable.suggest_pic_delete"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.2.20'
}
}
project.afterEvaluate {
def variants = null
try {
variants = android.applicationVariants
} catch (Throwable t) {
t.printStackTrace()
try {
variants = android.libraryVariants
} catch (Throwable tt) {
tt.printStackTrace()
}
}
if (variants != null) {
variants.all { variant ->
variant.outputs.each { output ->
def task = output.processManifestProvider.get()
if (task == null) {
return
}
/**
* 为 Manifest 的 Activity 的 configChanges 添加自己手动处理 configurationChanges 配置 [https://developer.android.com/guide/topics/resources/runtime-changes]
* AGP 4.1.0 从 ProcessManifest task 里拿 manifest 的 API 变更调整可以参考这里 [https://github.com/Tencent/tinker/pull/1476/commits/d71645729b13d545ca4ba6826f93fbf558751434]
* (搞半天还是不会抽离方法,有空再把 gradle 改成用 kotlin 实现吧)
*/
task.doLast {
def manifestFile = new File(multiApkManifestOutputDirectory.get().asFile, "AndroidManifest.xml")
if (manifestFile == null || !manifestFile.exists()) {
return
}
String[] configChanges = [
"density",
"fontScale",
"keyboard",
"keyboardHidden",
"layoutDirection",
"locale",
"mcc",
"mnc",
"navigation",
"orientation",
"screenLayout",
"screenSize",
"smallestScreenSize",
"touchscreen",
"uiMode"]
def parser = new XmlSlurper(false, true)
def manifest = parser.parse(manifestFile)
def app = manifest.'application'[0]
app.'activity'.each { act ->
String value = act.attributes()['android:configChanges']
if (value == null || value.isEmpty()) {
if (value == null) value = ""
configChanges.eachWithIndex { config, index ->
if (index != configChanges.length - 1) {
value += config + "|"
} else {
value += config
}
}
act.attributes()['androidconfigChanges'] = value
} else {
String[] valueSplit = value.split("\\|")
println configChanges
configChanges.eachWithIndex { config, index ->
if (!valueSplit.contains(config)) {
value += ("|" + config)
}
}
act.attributes()['android:configChanges'] = value
}
}
def tmpManifest = XmlUtil.serialize(manifest).replaceAll("androidconfigChanges", "android:configChanges")
manifest = parser.parseText(tmpManifest)
manifestFile.setText(XmlUtil.serialize(manifest), "utf-8")
}
}
}
}
}
// 依赖插件脚本
//apply from: 'tinker-support.gradle'

BIN
app/libs/EventBus-2.4.0.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
app/libs/libammsdk.jar Normal file

Binary file not shown.

BIN
app/libs/mid-sdk-2.3.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
app/libs/open_sdk_r5756.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,21 +0,0 @@
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.common.internal.DoNotStrip *;
}
# Keep native methods
-keepclassmembers class * {
native <methods>;
}
-dontwarn okio.**
-dontwarn com.squareup.okhttp.**
-dontwarn okhttp3.**
-dontwarn javax.annotation.**
-dontwarn com.android.volley.toolbox.**
-dontwarn com.facebook.infer.**

View File

@ -1,266 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
#--------- remove logs start ----------------
-assumenosideeffects class com.lightgame.config.CommonDebug {
private static String getLogTag(...);
private static String getMethodName();
public static void logMethodName(...);
public static void logParams(...);
public static void logFields(...);
public static void logMethodWithParams(...);
}
#-assumenosideeffects class com.lightgame.config.CommonDebug {*;}
#-dontoptimize
#--------- remove logs end ----------------
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod
-dontwarn InnerClasses
# OrmLite uses reflection
-keep class com.j256.**
-keepclassmembers class com.j256.** { *; }
-keep enum com.j256.**
-keepclassmembers enum com.j256.** { *; }
-keep interface com.j256.**
-keepclassmembers interface com.j256.** { *; }
-dontwarn com.j256.**
#okhttp3
-dontwarn com.squareup.okhttp3.**
-dontwarn okio.**
-keep class com.squareup.okhttp3.** { *;}
# stetho
-keep class com.facebook.stetho.** { *; }
-dontwarn com.facebook.stetho.**
# Retrofit 2.2
# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
# Retrofit 2.X
## https://square.github.io/retrofit/ ##
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
# rxjava
-keep class rx.schedulers.Schedulers {
public static <methods>;
}
-keep class rx.schedulers.ImmediateScheduler {
public <methods>;
}
-keep class rx.schedulers.TestScheduler {
public <methods>;
}
-keep class rx.schedulers.Schedulers {
public static ** test();
}
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
long producerNode;
long consumerNode;
}
-dontwarn rx.internal.util.**
## AutoScrollViewPager
-keep class cn.trinea.android.** { *; }
-keepclassmembers class cn.trinea.android.** { *; }
-dontwarn cn.trinea.android.**
## butterknife
# Retain generated class which implement Unbinder.
#-keep public class * implements butterknife.Unbinder { public <init>(**, android.view.View); }
#
## Prevent obfuscation of types which use ButterKnife annotations since the simple name
## is used to reflectively look up the generated ViewBinding.
#-keep class butterknife.*
#-keepclasseswithmembernames class * { @butterknife.* <methods>; }
#-keepclasseswithmembernames class * { @butterknife.* <fields>; }
-dontwarn butterknife.internal.**
-keep class **$$ViewInjector { *; }
-keepnames class * { @butterknife.InjectView *;}
-dontwarn butterknife.Views$InjectViewProcessor
-dontwarn com.gc.materialdesign.views.**
# eventbus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
# weiboSdk
-keep class com.sina.weibo.sdk.** { *; }
-dontwarn android.webkit.WebView
-dontwarn android.webkit.WebViewClient
# app models
-keep class com.gh.common.view.** {*;}
-keep class com.gh.gamecenter.db.info.** {*;}
-keep class com.gh.gamecenter.entity.** {*;}
-keep class com.gh.gamecenter.qa.entity.** {*;}
-keep class com.gh.gamecenter.retrofit.** {*;}
-keep class com.gh.gamecenter.eventbus.** {*;}
-keep class com.gh.gamecenter.video.detail.** {*;}
-keep class * extends rx.Subscriber
#---------------------------------webview------------------------------------
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String);
}
#----------------------------------------------------------------------------
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }
# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keepclassmembers enum * { *; }
##---------------End: proguard configuration for Gson ----------
# ------ bugly ---------
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# easypermission
-keepclassmembers class * {
@pub.devrel.easypermissions.AfterPermissionGranted <methods>;
}
# 重命名文件为SourceFile再配合mapping符号表可以拿到真实的类名
-renamesourcefileattribute SourceFile
# 保留源文件行号
-keepattributes SourceFile,LineNumberTable
-ignorewarnings
-keep @androidx.annotation.Keep class *
-keepclassmembers class ** {
@androidx.annotation.Keep *;
}
-keep class com.gh.loghub.** { *; }
### greenDAO 3
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
-keep class org.greenrobot.greendao.** { *; }
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use RxJava:
-dontwarn rx.**
-dontwarn org.greenrobot.greendao.rx.**
-dontwarn org.greenrobot.greendao.**
### fastJson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
-keepattributes Signature
-keepattributes Annotation
### AndroidX
-keep class androidx.core.app.CoreComponentFactory { *; }
#阿里云上传
-keep class com.alibaba.sdk.android.oss.** { *; }
-dontwarn okio.**
-dontwarn org.apache.commons.codec.binary.**
#视频相关
-keep class com.shuyu.gsyvideoplayer.video.** { *; }
-dontwarn com.shuyu.gsyvideoplayer.video.**
-keep class com.shuyu.gsyvideoplayer.video.base.** { *; }
-dontwarn com.shuyu.gsyvideoplayer.video.base.**
-keep class com.shuyu.gsyvideoplayer.utils.** { *; }
-dontwarn com.shuyu.gsyvideoplayer.utils.**
-keep class tv.danmaku.ijk.** { *; }
-dontwarn tv.danmaku.ijk.**
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#穿山甲
-keep class com.bytedance.sdk.openadsdk.** { *; }
-keep public interface com.bytedance.sdk.openadsdk.downloadnew.** {*;}
-keep class com.pgl.sys.ces.* {*;}
-keep class com.gyf.immersionbar.* {*;}
-dontwarn com.gyf.immersionbar.**
-keep class com.taobao.securityjni.**{*;}
-keep class com.taobao.wireless.security.**{*;}
-keep class com.ut.secbody.**{*;}
-keep class com.taobao.dp.**{*;}
-keep class com.alibaba.wireless.security.**{*;}
-keep class com.alibaba.sdk.android.**{*;}
-keep class com.ut.**{*;}
-keep class com.ta.**{*;}
-keep class com.gh.gamecenter.TeaHelper { *; }

17
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -1,157 +0,0 @@
#--------- remove logs start ----------------
-assumenosideeffects class com.lightgame.config.CommonDebug {
private static String getLogTag(...);
private static String getMethodName();
public static void logMethodName(...);
public static void logParams(...);
public static void logFields(...);
public static void logMethodWithParams(...);
}
-assumenosideeffects class com.lightgame.utils.Utils {
public static void log(...);
}
#--------- remove logs end ----------------
#--------- remove useless mtahelper class --------
-assumenosideeffects class com.gh.common.util.MtaHelper {
public static void onEvent(...);
public static void onEventWithTime(...);
public static void onEventWithBasicDeviceInfo(...);
}
#--------- remove useless mta class end ----
# TODO Dicard sourceFile in final release build but remain in internal build.
-renamesourcefileattribute SourceFile
# Keep Attribute
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod,SourceFile,LineNumberTable
# OrmLite
-keep class com.j256.*
-keepclassmembers class com.j256.* { *; }
-keep enum com.j256.*
-keepclassmembers enum com.j256.* { *; }
-keep interface com.j256.*
-keepclassmembers interface com.j256.* { *; }
-dontwarn com.j256.**
### AutoScrollViewPager
-keep class cn.trinea.android.* { *; }
-keepclassmembers class cn.trinea.android.* { *; }
-dontwarn cn.trinea.android.**
### eventbus
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
### Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
### weiboSdk
-keep class com.sina.weibo.sdk.** { *; }
-dontwarn android.webkit.WebView
-dontwarn android.webkit.WebViewClient
### wechatSdk
### TODO 这里用 com.tencent.*{*;} 不起效?但其它地方可以?
-keep class com.tencent.**{*;}
### app models
-keep class com.gh.common.view.* {*;}
-keep class com.gh.gamecenter.db.info.* {*;}
-keep class com.gh.gamecenter.entity.* {*;}
-keep class com.gh.gamecenter.qa.entity.* {*;}
-keep class com.gh.gamecenter.retrofit.* {*;}
-keep class com.gh.gamecenter.eventbus.* {*;}
-keep class com.gh.gamecenter.video.detail.* {*;}
-keep class com.gh.gamecenter.home.gamecollection.* {*;}
###
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String);
}
### easypermission
-keepclassmembers class * {
@pub.devrel.easypermissions.AfterPermissionGranted <methods>;
}
# TODO What's this ?
-ignorewarnings
### Keep Annotation
-keep @androidx.annotation.Keep class *
-keepclassmembers class * {
@androidx.annotation.Keep *;
}
### 阿里云上传
-keep class com.alibaba.sdk.android.oss.* { *; }
-dontwarn okio.**
-dontwarn org.apache.commons.codec.binary.**
### 视频相关
-keep class com.shuyu.gsyvideoplayer.video.* { *; }
-dontwarn com.shuyu.gsyvideoplayer.video.**
-keep class com.shuyu.gsyvideoplayer.video.base.* { *; }
-dontwarn com.shuyu.gsyvideoplayer.video.base.**
-keep class com.shuyu.gsyvideoplayer.utils.* { *; }
-dontwarn com.shuyu.gsyvideoplayer.utils.**
-keep class tv.danmaku.ijk.* { *; }
-dontwarn tv.danmaku.ijk.**
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keep class com.alibaba.sdk.android.*{*;}
-keep class com.ut.*{*;}
-keep class com.ta.*{*;}
### TEA
-keep class com.gh.gamecenter.TeaHelper { *; }
### 阿里云日志
-keep class com.aliyun.sls.android.producer.* { *; }
-keep interface com.aliyun.sls.android.producer.* { *; }
### 中国移动一键登录
-dontwarn com.cmic.sso.sdk.**
-keep class com.cmic.sso.sdk.* { *; }
### EasyFloat
-keep class com.lzf.easyfloat.* {*;}
### 避免 WebChromeClient 被混淆
-keepclassmembers class * extends android.webkit.WebChromeClient{
public void openFileChooser(...);
}
### emoji4j
-keep class emoji4j.* {*;}
### dokit
-keep class com.didichuxing.** {*;}
# Flutter模块
-keep class com.gh.common.util.DirectUtils {
public static void directToQa(...);
public static void directToQaCollection(...);
public static void directToGift(...);
public static void directToConcernInfo(...);
public static void directToFeedback(...);
public static void directToSuggestion(...);
}

View File

@ -1,4 +0,0 @@
storeFile=gh.keystore
storePassword=20150318
keyAlias=gh.keystore
keyPassword=20150318

View File

@ -0,0 +1,13 @@
package com.gh.gamecenter;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@ -1,32 +0,0 @@
package com.gh.gamecenter;
import android.app.Application;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
/**
* @author CsHeng
* @Date 03/09/2017
* @Time 4:34 PM
*/
public class Injection {
public static boolean appInit(Application application) {
// 监控Bundle大小,预防溢出(需要调试的时候再开启吧!)
// TooLargeTool.startLogging(application);
return true;
}
public static OkHttpClient.Builder provideRetrofitBuilder() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addNetworkInterceptor(interceptor);
return builder;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="utf-8">
<title>光环助手</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<style>
body {
font: 100%/1.0 'Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif;
background-color: #fff;
margin: 0;
padding: 0;
}
header {
}
article {
width:100%;
max-width:720px;
clear: both;
margin: 0 auto;
margin-top: 20%;
text-align: center;
margin-bottom:20%;
}
.title{margin-top: 4%;font-size:1.7em;color:#191919;text-align:center;}
.info{margin-top: 18%;font-size:1.0em;color:#191919;line-height:1.3em;}
.download {text-align: center;}
.download a{font-size:1.8em;padding:0.2em; text-align:center;color:#ffffff;margin: 0 auto;width:56%;background-color:#2999f9;border-radius:8px; text-decoration:none;display:block;line-height:1.8em;}
@media only screen and (min-width: 1080px) {
article {
width:100%;
max-width:720px;
clear: both;
margin: 0 auto;
margin-top: 5%;
text-align: center;
margin-bottom:20%;
}
}
</style>
</head>
<body>
<header>
</header>
<article>
<img src="http://192.168.43.1:3100/image/gh_icon.png" width="28%">
<p class="title">光环助手</p>
<br class="info">乐于分享的人是最帅的^_^ </p>
<div class="download">
<a href="http://192.168.43.1:3100/download/ghzs.apk">免流量下载</a>
</div>
<p class="title"><font color="#9A9A9A" size="3em">仅限安卓系统 </font></p>
</article>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,163 +0,0 @@
function requestContentFocus() {
$("#editor").focus();
}
function setupWhenContentEditable() {
var editor = $("#editor");
if (!editor[0].hasAttribute("contenteditable")) {
return;
}
// paste
editor.on("paste", function(e) {
e.preventDefault();
var text = (e.originalEvent || e).clipboardData.getData("text/plain");
text = text.replace(/\n/g, "<br>");
if ("" != text) {
document.execCommand("insertHTML", false, text);
} else {
window.onPasteListener.onPaste();
}
});
requestContentFocus();
}
function getStyle(dom, name) {
return window.getComputedStyle(dom)[name];
}
function customLinkgo(self) {
var datas = self.dataset.datas;
console.log(datas)
window.OnLinkClickListener.onClick(datas);
}
var typeClassList = [
"community_article-container",
"answer-container",
"game-container"
];
function removeDomByParent(curDom) {
if (curDom.parentElement) {
curDom.parentElement.removeChild(curDom);
}
}
window.addEventListener("load", function() {
var EditorDom = document.querySelector("#editor");
setupWhenContentEditable();
document.addEventListener("keydown", function(e) {
var event = e || window.event;
if (event.keyCode === 8) {
var s = document.getSelection();
var r = s.getRangeAt(0);
if (r.startOffset === r.endOffset && r.endOffset === 0) {
var preDOM = s.focusNode.previousElementSibling;
if (
preDOM &&
preDOM instanceof Element &&
preDOM.nodeName === "IMG" &&
getStyle(preDOM, "display") === "block"
) {
preDOM.parentElement.removeChild(preDOM);
}
}
var customDom = s.focusNode;
if (customDom) {
if (
r.startContainer.nodeName.toLowerCase() === "blockquote" &&
r.startOffset === 0
) {
RE.formatBlock();
e.preventDefault();
} else if (
customDom.nodeName === "#text" &&
customDom.previousElementSibling &&
typeClassList.indexOf(customDom.previousElementSibling.className) >
-1 &&
r.startOffset === 1
) {
var needDeleteDom = customDom.previousElementSibling;
needDeleteDom.insertAdjacentElement(
"afterend",
document.createElement("br")
);
} else if (
customDom instanceof Element &&
customDom.childNodes[s.focusOffset] &&
customDom.childNodes[s.focusOffset].previousElementSibling &&
typeClassList.indexOf(
customDom.childNodes[s.focusOffset].previousElementSibling.className
) > -1
) {
customDom =
customDom.childNodes[s.focusOffset].previousElementSibling;
customDom.parentElement.removeChild(customDom);
}
}
}
});
document.addEventListener("keyup", function(e) {
var event = e || window.event;
if (event.keyCode === 13) {
var s = document.getSelection();
var curDom = s.focusNode;
var preDom = curDom.previousElementSibling;
if (
curDom.nodeName.toLowerCase() === "blockquote" &&
preDom.nodeName.toLowerCase() === "blockquote"
) {
if (
preDom.childNodes.length > 1 ||
(preDom.childNodes.length === 1 &&
preDom.childNodes[0].tagName !== "BR")
) {
curDom.style.marginTop = 0;
preDom.style.marginBottom = 0;
} else if (
(curDom.childNodes.length === 0 ||
(curDom.childNodes.length === 1 &&
curDom.childNodes[0].tagName === "BR")) &&
(preDom.childNodes.length === 0 ||
(preDom.childNodes.length === 1 &&
preDom.childNodes[0].tagName === "BR"))
) {
removeDomByParent(curDom);
var startQuoteDom = preDom.previousElementSibling;
startQuoteDom && startQuoteDom.nodeName.toLowerCase() === "blockquote"
? (startQuoteDom.style.marginBottom = "10px")
: null;
var range = document.createRange();
range.selectNode(preDom);
s.removeAllRanges();
s.addRange(range);
RE.formatBlock();
}
}
}
});
document.addEventListener("selectionchange", function(e) {
var event = e || window.event;
var targetDom = event.target.activeElement;
if (targetDom.id === "editor" && targetDom.lastElementChild) {
if (typeClassList.indexOf(targetDom.lastElementChild.className) > -1) {
var brDom = document.createElement("br");
EditorDom.appendChild(brDom);
}
}
});
});

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="normalize.css">
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="video-js.min.css">
<!-- <link rel="stylesheet" href="https://static-web.ghzs.com/website-static/lib/video-js.min.css">--> <!--在web页面播放视频-->
<!--<link rel="stylesheet" type="text/css" href="https://resource.ghzs.com/css/halo_app.css">-->
</head>
<body style="overflow-x: hidden; word-break: break-all;">
<div id="editor" contenteditable="false"></div>
<script type="text/javascript" src="zepto.min.js"></script>
<script type="text/javascript" src="rich_editor.js"></script>
<script type="text/javascript" src="video.min.js"></script>
<!--<script src="https://static-web.ghzs.com/website-static/lib/video.min.js"></script>--> <!--在web页面播放视频-->
<!--<script type="text/javascript" src="content.js"></script>-->
<!--<script type="text/javascript" src="https://resource.ghzs.com/js/halo_app.js"></script>-->
</body>
</html>

View File

@ -1 +0,0 @@
{"v":"5.5.9","fr":60,"ip":0,"op":90,"w":1080,"h":202,"nm":"click","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"椭圆形","sr":1,"ks":{"o":{"a":0,"k":20,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[204,1455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"椭圆形","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":63,"s":[10]},{"t":70,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[204,1455,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":39,"s":[100,100,100]},{"t":49,"s":[110,110,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[36,36],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"圆环","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.531],"y":[0]},"t":28,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":38,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.526],"y":[0]},"t":48,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.446],"y":[0]},"t":63,"s":[50]},{"t":82,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[125.951,79.658,0],"ix":2},"a":{"a":0,"k":[205.951,1458.658,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.601,0.601,0.333],"y":[0,0,0]},"t":28,"s":[50,50,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.528,0.528,0.333],"y":[0,0,0]},"t":38,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.526,0.526,0.333],"y":[0,0,0]},"t":48,"s":[120,120,100]},{"t":63,"s":[100,100,100]}],"ix":6}},"ao":0,"w":1080,"h":1920,"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"点击手","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.596],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.515],"y":[0]},"t":63,"s":[100]},{"t":83,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.507],"y":[0]},"t":10,"s":[6]},{"t":30,"s":[2]}],"ix":10},"p":{"a":0,"k":[178.982,123.325,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.489,0.489,0.333],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"t":30,"s":[90,90,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.33,-8.3],[3.89,0.27],[-4.4,-1.68],[-4.33,-0.67],[-4.08,9.32],[3.33,5.44],[3.39,4.6],[0.87,-3.7],[3.6,-0.86],[1.03,-0.21],[2.34,-0.53],[0.96,1.15],[4.22,5.48],[-1.18,-4.56]],"o":[[1.11,1.71],[-3.89,-0.27],[6.42,2.5],[4.33,0.66],[1.63,-5.32],[-3.34,-5.45],[-1.68,-2.1],[-0.71,3.14],[-3.43,0.95],[-0.57,0.08],[-3.86,1.12],[-3.23,-3.94],[-1.89,-2.28],[2.42,4.64]],"v":[[-5.387,9.698],[-10.717,8.498],[-12.327,15.628],[5.813,21.748],[23.313,11.778],[20.273,-1.202],[11.563,-13.962],[5.393,-12.362],[1.083,-13.722],[-2.087,-9.742],[-5.707,-11.752],[-8.777,-7.572],[-18.297,-20.542],[-23.827,-17.832]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径备份 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}],"markers":[]}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"v":"5.6.4","fr":25,"ip":0,"op":35,"w":1080,"h":214,"nm":"点赞","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"椭圆形 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.588],"y":[0]},"t":17,"s":[15]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[468.04,73.68,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.659,0.659,0.333],"y":[0,0,0]},"t":10,"s":[50,50,100]},{"t":19,"s":[150,150,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":1510,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"椭圆形 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.588],"y":[0]},"t":27,"s":[15]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[468.04,73.68,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.659,0.659,0.333],"y":[0,0,0]},"t":20,"s":[50,50,100]},{"t":28,"s":[140,140,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":20,"op":1520,"st":20,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"路径备份 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.602],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.663],"y":[0]},"t":24,"s":[100]},{"t":29,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[531.02,129.675,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.514,0.514,0.333],"y":[0,0,0]},"t":5,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.533,0.533,0.333],"y":[0,0,0]},"t":10,"s":[90,90,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.586,0.586,0.333],"y":[0,0,0]},"t":15,"s":[95,95,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.499,0.499,0.333],"y":[0,0,0]},"t":19,"s":[90,90,100]},{"t":24,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.33,-8.3],[3.89,0.27],[-4.4,-1.68],[-4.33,-0.67],[-4.08,9.32],[3.33,5.44],[3.39,4.6],[0.87,-3.7],[3.6,-0.86],[1.03,-0.21],[2.34,-0.53],[0.96,1.15],[4.22,5.48],[-1.18,-4.56]],"o":[[1.11,1.71],[-3.89,-0.27],[6.42,2.5],[4.33,0.66],[1.63,-5.32],[-3.34,-5.45],[-1.68,-2.1],[-0.71,3.14],[-3.43,0.95],[-0.57,0.08],[-3.86,1.12],[-3.23,-3.94],[-1.89,-2.28],[2.42,4.64]],"v":[[-5.387,9.698],[-10.717,8.498],[-12.327,15.628],[5.813,21.748],[23.313,11.778],[20.273,-1.202],[11.563,-13.962],[5.393,-12.362],[1.083,-13.722],[-2.087,-9.742],[-5.707,-11.752],[-8.777,-7.572],[-18.297,-20.542],[-23.827,-17.832]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径备份 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0}],"markers":[]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"v":"5.6.9","fr":60,"ip":0,"op":76,"w":90,"h":90,"nm":"loading","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"圆环1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[63,63],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.75],"y":[0.833]},"o":{"x":[0.25],"y":[0.167]},"t":28,"s":[0]},{"t":76,"s":[96]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.75],"y":[0.833]},"o":{"x":[0.25],"y":[0.167]},"t":0,"s":[4]},{"t":48,"s":[100]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.75],"y":[0.712]},"o":{"x":[0.25],"y":[0.288]},"t":0,"s":[-7.2]},{"t":76,"s":[367.2]}],"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.141176477075,0.588235318661,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":76,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"圆环2","sr":1,"ks":{"o":{"a":0,"k":40,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[63,63],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.75],"y":[0.861]},"o":{"x":[0.25],"y":[0.139]},"t":36,"s":[0]},{"t":76,"s":[96]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.75],"y":[0.806]},"o":{"x":[0.25],"y":[0.194]},"t":0,"s":[4]},{"t":56,"s":[100]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.75],"y":[0.712]},"o":{"x":[0.25],"y":[0.288]},"t":0,"s":[-7.2]},{"t":76,"s":[367.2]}],"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.141176477075,0.588235318661,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":76,"st":0,"bm":0}],"markers":[{"tm":-5940,"cm":"S:[false]","dr":0},{"tm":0.740234375,"cm":"FOCUSED -- TO SELECTION","dr":0}]}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"v":"5.5.9","fr":60,"ip":0,"op":120,"w":1080,"h":586,"nm":"上滑","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"手","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.642],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.558],"y":[0]},"t":60,"s":[100]},{"t":71,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.479,"y":0},"t":3,"s":[611,475,0],"to":[0,-62.75,0],"ti":[0,62.75,0]},{"t":40,"s":[611,98.5,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[90,90,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.78,-12.06],[5.68,0.39],[-6.42,-2.45],[-6.32,-0.97],[-5.95,13.55],[4.87,7.91],[4.94,6.69],[1.26,-5.37],[5.25,-1.25],[1.5,-0.3],[3.4,-0.77],[1.4,1.68],[6.15,7.98],[-1.73,-6.64]],"o":[[1.62,2.49],[-5.68,-0.39],[9.37,3.63],[6.31,0.98],[2.39,-7.74],[-4.87,-7.92],[-2.45,-3.05],[-1.05,4.57],[-4.99,1.39],[-0.83,0.13],[-5.63,1.63],[-4.71,-5.72],[-2.75,-3.31],[3.52,6.76]],"v":[[-7.86,14.26],[-15.64,12.52],[-17.99,22.89],[8.47,31.78],[33.98,17.29],[29.55,-1.59],[16.85,-20.15],[7.86,-17.83],[1.56,-19.8],[-3.05,-14.02],[-8.33,-16.94],[-12.44,-11.045],[-26.761,-30.19],[-34.75,-25.78]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"矩形","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.715],"y":[0]},"t":57,"s":[100]},{"t":67,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[525,228.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.491,"y":0},"t":3,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[24.328,180.5],[-22,180.5],[-22,204.5],[24.328,204.5]],"c":true}]},{"t":40,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[24.328,-207.5],[-22,-207.5],[-22,204.5],[24.328,204.5]],"c":true}]}],"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"蒙版 1"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[6,137],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1,0,1,0.5,0.5,1,0],"ix":9}},"s":{"a":0,"k":[0,-68.5],"ix":5},"e":{"a":0,"k":[0,68.5],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":33,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":123,"st":3,"bm":0}],"markers":[]}

View File

@ -1 +0,0 @@
{"v":"5.6.9","fr":60,"ip":0,"op":36,"w":120,"h":66,"nm":"开关动画-关闭","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"按钮手柄","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.667,"y":0},"t":0,"s":[87,33,0],"to":[7.682,0,0],"ti":[-13.443,0,0]},{"i":{"x":0.333,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[31,33,0],"to":[2.306,0,0],"ti":[-1.318,0,0]},{"t":24,"s":[33,33,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"指示器-on","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.667],"y":[0]},"t":0,"s":[100]},{"t":18,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.667,0.667],"y":[0,0]},"t":0,"s":[6.5,6.5]},{"t":18,"s":[4.5,4.5]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.966666666667,0.966666666667,0.966666666667,0.420000005762],"ix":3},"o":{"a":0,"k":40,"ix":4},"w":{"a":0,"k":1.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"指示器-off","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[87,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.667,0.667],"y":[0,0]},"t":0,"s":[1.5,4]},{"t":18,"s":[1.5,6]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0.75,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":5,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.667],"y":[0]},"t":0,"s":[0]},{"t":18,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"按钮背景","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sy":[{"c":{"a":0,"k":[0,0,0,1],"ix":2},"o":{"a":0,"k":5,"ix":3},"a":{"a":0,"k":120,"ix":5},"s":{"a":0,"k":1,"ix":8},"d":{"a":0,"k":0,"ix":6},"ch":{"a":0,"k":100,"ix":7},"bm":{"a":0,"k":5,"ix":1},"no":{"a":0,"k":0,"ix":9},"ty":2,"nm":"内阴影"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,22],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.667],"y":[0]},"t":0,"s":[0.141176477075,0.588235318661,1,1]},{"t":18,"s":[0.933333337307,0.933333337307,0.933333337307,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"filling","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.118,0.006],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0}],"markers":[]}

View File

@ -1 +0,0 @@
{"v":"5.6.9","fr":60,"ip":0,"op":36,"w":120,"h":66,"nm":"开关动画-打开","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"按钮手柄","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.667,"y":0},"t":0,"s":[33,33,0],"to":[7.682,0,0],"ti":[-13.443,0,0]},{"i":{"x":0.333,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[89,33,0],"to":[2.306,0,0],"ti":[-1.318,0,0]},{"t":24,"s":[87,33,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"指示器-on","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.667],"y":[0]},"t":0,"s":[0]},{"t":18,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.667,0.667],"y":[0,0]},"t":0,"s":[4.5,4.5]},{"t":18,"s":[6.5,6.5]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.966666666667,0.966666666667,0.966666666667,0.420000005762],"ix":3},"o":{"a":0,"k":40,"ix":4},"w":{"a":0,"k":1.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"指示器-off","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[87,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.667,0.667],"y":[0,0]},"t":0,"s":[1.5,6]},{"t":18,"s":[1.5,4]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0.75,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":5,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.667],"y":[0]},"t":0,"s":[100]},{"t":18,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"按钮背景","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sy":[{"c":{"a":0,"k":[0,0,0,1],"ix":2},"o":{"a":0,"k":5,"ix":3},"a":{"a":0,"k":120,"ix":5},"s":{"a":0,"k":1,"ix":8},"d":{"a":0,"k":0,"ix":6},"ch":{"a":0,"k":100,"ix":7},"bm":{"a":0,"k":5,"ix":1},"no":{"a":0,"k":0,"ix":9},"ty":2,"nm":"内阴影"}],"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[40,22],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"矩形路径 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.667],"y":[0]},"t":0,"s":[0.933332979679,0.933332979679,0.933332979679,1]},{"t":18,"s":[0.141176477075,0.588235318661,1,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"filling","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.118,0.006],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":37,"st":0,"bm":0}],"markers":[]}

View File

@ -1 +0,0 @@
{"v":"5.6.9","fr":30,"ip":0,"op":20,"w":66,"h":66,"nm":"bottom bar tab/论坛/选中/E","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"白-修正","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":9,"s":[110,110,100]},{"t":13,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[0,-0.66],[0,0],[1.38,0],[0,1.38],[0,0],[-0.66,0],[0,0]],"o":[[0,0],[0,1.38],[-1.38,0],[0,0],[0,-0.66],[0,0],[0.66,0]],"v":[[2.5,-1.3],[2.5,0],[0,2.5],[-2.5,0],[-2.5,-1.3],[-1.3,-2.5],[1.3,-2.5]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":4,"s":[{"i":[[0,-0.66],[0,0],[1.38,0],[0,1.38],[0,0],[-0.66,0],[0,0]],"o":[[0,0],[0,1.38],[-1.38,0],[0,0],[0,-0.66],[0,0],[0.66,0]],"v":[[2.5,-0.102],[2.5,0],[0,2.109],[-2.5,0],[-2.5,-0.102],[-1.3,-1.302],[1.3,-1.302]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":9,"s":[{"i":[[0,-0.66],[0,0],[1.38,0],[0,1.38],[0,0],[-0.66,0],[0,0]],"o":[[0,0],[0,1.38],[-1.38,0],[0,0],[0,-0.66],[0,0],[0.66,0]],"v":[[2.498,-1.845],[2.5,0],[0,2.734],[-2.5,0],[-2.502,-1.845],[-1.302,-3.045],[1.298,-3.045]],"c":true}]},{"t":13,"s":[{"i":[[0,-0.66],[0,0],[1.38,0],[0,1.38],[0,0],[-0.66,0],[0,0]],"o":[[0,0],[0,1.38],[-1.38,0],[0,0],[0,-0.66],[0,0],[0.66,0]],"v":[[2.5,-1.3],[2.5,0],[0,2.5],[-2.5,0],[-2.5,-1.3],[-1.3,-2.5],[1.3,-2.5]],"c":true}]}],"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"蓝","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,33.76,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.8,0.42],[-3.44,-0.79],[-0.09,-1.71],[0,0],[0,0],[1.98,-0.1],[0,0],[0,0],[0,0],[0.62,0.57],[0,0],[0,0],[0,0],[0,0],[0,0],[0.2,1.89],[0,0],[0,0],[0,0]],"o":[[3.44,-0.79],[1.74,0.41],[0,0],[0,0],[0,2],[0,0],[0,0],[0,0],[-0.69,0.52],[0,0],[0,0],[0,0],[0,0],[0,0],[-1.94,0],[0,0],[0,0],[0,0],[0,-1.8]],"v":[[-6.39,-8.971],[6.39,-8.971],[9.49,-5.471],[9.5,-5.271],[9.5,3.249],[5.95,6.989],[5.75,6.999],[3.25,6.999],[0.3,9.209],[-1.95,9.089],[-2.06,8.969],[-2.17,8.849],[-2.21,8.779],[-3.4,6.999],[-5.75,6.999],[-9.48,3.639],[-9.49,3.449],[-9.5,3.249],[-9.5,-5.271]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.266,0.638,1,0.5,0.242,0.595,1,1,0.217,0.552,1],"ix":9}},"s":{"a":0,"k":[-9.5,-9.561],"ix":5},"e":{"a":0,"k":[9.5,9.561],"ix":6},"t":1,"nm":"color","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"预合成 1","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,33,0],"ix":2},"a":{"a":0,"k":[33,33,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":4,"s":[70,70,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":9,"s":[110,110,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":13,"s":[90,90,100]},{"t":16,"s":[100,100,100]}],"ix":6}},"ao":0,"w":66,"h":66,"ip":0,"op":20,"st":0,"bm":0}],"markers":[]}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"v":"5.5.9","fr":30,"ip":0,"op":20,"w":66,"h":66,"nm":"tab_index","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"椭圆形备份","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.596],"y":[0]},"t":0,"s":[0]},{"t":6,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,40.493,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.54,0.54,0.333],"y":[0,0,0]},"t":4,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.555,0.555,0.333],"y":[0,0,0]},"t":9,"s":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.552,0.552,0.333],"y":[0,0,0]},"t":13,"s":[90,90,100]},{"t":16,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[5,5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"椭圆路径 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.635],"y":[0]},"t":0,"s":[0]},{"t":8,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"椭圆形备份","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-0.5,"op":59.5,"st":-0.5,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"路径备份","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,32.993,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.508,0.508,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.488,0.488,0.333],"y":[0,0,0]},"t":4,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.502,0.502,0.333],"y":[0,0,0]},"t":9,"s":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.534,0.534,0.333],"y":[0,0,0]},"t":13,"s":[95,95,100]},{"t":16,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.24,-1.21],[-1.93,-1.89],[-0.24,-0.57],[0,-0.06],[0,-2.63],[1.76,0],[0,0],[0,1.72],[0,2.63],[-0.24,0.61],[-0.03,0.02],[-1.93,1.89]],"o":[[1.92,1.89],[0.02,0.02],[0.24,0.57],[0,2.63],[0,1.72],[0,0],[-1.76,0],[0,-2.62],[0,-0.07],[0.25,-0.61],[1.92,-1.89],[1.24,-1.21]],"v":[[2.26,-9.09],[8.03,-3.42],[8.76,-2.38],[9,-1.02],[9,6.88],[5.82,10],[-5.82,10],[-9,6.88],[-9,-0.99],[-8.71,-2.38],[-8.01,-3.43],[-2.23,-9.09]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.266,0.638,1,0.5,0.242,0.595,1,1,0.217,0.552,1],"ix":9}},"s":{"a":0,"k":[-4.902,-4.663],"ix":5},"e":{"a":0,"k":[8.159,8.646],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径备份","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]}

View File

@ -1 +0,0 @@
{"v":"5.5.9","fr":30,"ip":0,"op":20,"w":66,"h":66,"nm":"tab_video","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"形状图层 1","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.771],"y":[0]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-3.742,6.835,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[30.937,31.042,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":29.286,"ix":7},"os":{"a":0,"k":75,"ix":9},"ix":1,"nm":"多边星形路径 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":13,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"描边 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-20.75,-13.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.743,88.578],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"多边星形 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.657],"y":[0]},"t":0,"s":[0]},{"t":8,"s":[100]}],"ix":2},"o":{"a":0,"k":-115,"ix":3},"m":1,"ix":2,"nm":"修剪路径 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"路径 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[33,33.004,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.508,0.508,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.488,0.488,0.333],"y":[0,0,0]},"t":4,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.502,0.502,0.333],"y":[0,0,0]},"t":9,"s":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.534,0.534,0.333],"y":[0,0,0]},"t":13,"s":[95,95,100]},{"t":16,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[2.16,0.38],[3.22,-0.55],[0.38,-2.16],[-0.55,-3.22],[-2.16,-0.38],[-1.63,0],[-1.61,0.27],[-0.38,2.16],[0.55,3.22]],"o":[[-0.38,-2.16],[-3.22,-0.55],[-2.16,0.38],[-0.55,3.22],[0.38,2.16],[1.61,0.27],[1.63,0],[2.16,-0.38],[0.55,-3.22],[0,0]],"v":[[9.09,-4.86],[4.86,-9.09],[-4.86,-9.09],[-9.09,-4.86],[-9.09,4.86],[-4.86,9.09],[0,9.5],[4.86,9.09],[9.09,4.86],[9.09,-4.86]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.266,0.638,1,0.5,0.242,0.595,1,1,0.217,0.552,1],"ix":9}},"s":{"a":0,"k":[-5.174,-4.43],"ix":5},"e":{"a":0,"k":[8.612,8.214],"ix":6},"t":1,"nm":"Gradient Fill 3","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"变换"}],"nm":"路径","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]}

View File

@ -1,413 +0,0 @@
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}

View File

@ -1,132 +0,0 @@
[
{
"login": {
"title": "开启消息通知",
"content": "新游上线、互动回复,重要推送不错过",
"image": "bg_notification_login_style_1",
"styleNo": "样式A",
"scenes": "场景1"
},
"question": {
"title": "开启消息通知",
"content": "及时查看大神回答",
"image": "bg_notification_question_style_1",
"styleNo": "样式A",
"scenes": "场景2"
},
"answer": {
"title": "开启消息通知",
"content": "及时查看点赞与评论",
"image": "bg_notification_answer_style_1",
"styleNo": "样式A",
"scenes": "场景3"
},
"article": {
"title": "开启消息通知",
"content": "及时查看点赞与评论",
"image": "bg_notification_article_style_1",
"styleNo": "样式A",
"scenes": "场景4"
},
"video": {
"title": "开启消息通知",
"content": "实时获取审核与推荐进度",
"image": "bg_notification_video_style_1",
"styleNo": "样式A",
"scenes": "场景5"
},
"rating": {
"title": "开启消息通知",
"content": "成功上墙立即知道",
"image": "bg_notification_rating_style_1",
"styleNo": "样式A",
"scenes": "场景6"
},
"gift": {
"title": "开启消息通知",
"content": "新上礼包不再错过",
"image": "bg_notification_gift_style_1",
"styleNo": "样式A",
"scenes": "场景7"
},
"reserveGame": {
"title": "开启消息通知",
"content": "新游上线即时体验",
"image": "bg_notification_reserve_game_style_1",
"styleNo": "样式A",
"scenes": "场景8"
},
"feedback": {
"title": "开启消息通知",
"content": "及时查看客服回复",
"image": "bg_notification_feedback_style_1",
"styleNo": "样式A",
"scenes": "场景9"
}
},
{
"login": {
"title": "咦!是新的小伙伴耶!",
"content": "打开<font color=\"#1383EB\">通知开关</font>,游戏、礼包、抽奖活动不错过",
"image": "bg_notification_login_style_2",
"styleNo": "样式B",
"scenes": "场景1"
},
"question": {
"title": "发布成功!答案马上来!",
"content": "为了第一时间通知您,需要打开<font color=\"#1383EB\">通知开关</font>",
"image": "bg_notification_question_style_2",
"styleNo": "样式B",
"scenes": "场景2"
},
"answer": {
"title": "精彩的回答!大佬牛啤!",
"content": "打开<font color=\"#1383EB\">通知开关</font>,可以第一时间收获赞美和感谢哟!",
"image": "bg_notification_answer_style_2",
"styleNo": "样式B",
"scenes": "场景3"
},
"article": {
"title": "发布成功!不愧是你!",
"content": "打开<font color=\"#1383EB\">通知开关</font>,可以第一时间收获赞美和互动哟!",
"image": "bg_notification_article_style_2",
"styleNo": "样式B",
"scenes": "场景4"
},
"video": {
"title": "“百万”播放预定!",
"content": "<font color=\"#1383EB\">打开通知!</font>第一时间知道审核结果和互动信息哟!",
"image": "bg_notification_video_style_2",
"styleNo": "样式B",
"scenes": "场景5"
},
"rating": {
"title": "这游戏超好玩,我说的!",
"content": "想知道有多少人吃下安利?<font color=\"#1383EB\">打开通知</font>,马上知道!",
"image": "bg_notification_rating_style_2",
"styleNo": "样式B",
"scenes": "场景6"
},
"gift": {
"title": "获得道具:神奇的游戏礼包!",
"content": "<font color=\"#1383EB\">打开通知!</font>礼包上线,马上知道!",
"image": "bg_notification_gift_style_2",
"styleNo": "样式B",
"scenes": "场景7"
},
"reserveGame": {
"title": "玩最新的游戏,做游戏圈最靓的仔",
"content": "<font color=\"#1383EB\">打开通知!</font>游戏上线,更快知道!",
"image": "bg_notification_reserve_game_style_2",
"styleNo": "样式B",
"scenes": "场景8"
},
"feedback": {
"title": "真是重要的反馈!",
"content": "感恩有你,光环更精彩!<font color=\"#1383EB\">打开通知</font>,客服回复,马上知道!",
"image": "bg_notification_feedback_style_2",
"styleNo": "样式B",
"scenes": "场景9"
}
}
]

File diff suppressed because it is too large Load Diff

View File

@ -1,636 +0,0 @@
/**
* Copyright (C) 2017 Wasabeef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// alert("")
var RE = {};
RE.currentSelection = {
"startContainer": 0,
"startOffset": 0,
"endContainer": 0,
"endOffset": 0};
var isDebug = false;
try {
isDebug = window.NativeCallBack.isNativeBuildDebug()
} catch(error) {
}
// 引用远端的JS 和 CSS
var script = document.createElement("script")
document.body.appendChild(script)
if (isDebug) {
script.src = "https://resource.ghzs.com/js/halo_app_test.js" + "?timestamp=" + Math.round(new Date().getTime() / 1000)
} else {
script.src = "https://resource.ghzs.com/js/halo.js" + "?timestamp=" + Math.round(new Date().getTime() / 1000 / 1000)
}
var style = document.createElement("link")
style.rel = "stylesheet"
style.type = "text/css"
if (isDebug) {
style.href = "https://resource.ghzs.com/css/halo_app_test.css" + "?timestamp=" + Math.round(new Date().getTime() / 1000)
} else {
style.href = "https://resource.ghzs.com/css/halo.css" + "?timestamp=" + Math.round(new Date().getTime() / 1000 / 1000)
}
document.head.appendChild(style)
RE.editor = document.getElementById('editor');
document.addEventListener("selectionchange", function() { RE.backuprange(); });
// Initializations
RE.callback = function() {
window.location.href = "re-callback://" + encodeURIComponent(RE.getHtml());
}
RE.setHtml = function(contents) {
RE.editor.innerHTML = decodeURIComponent(contents.replace(/\+/g, '%20'));
}
// 后续初始化html代码,都用该方法
RE.setHtmlByVideoStatus = function(contents) {
RE.editor.innerHTML = decodeURIComponent(contents.replace(/\+/g, '%20'));
}
// Deprecated
RE.getHtml = function() {
return RE.editor.innerHTML;
}
RE.getText = function() {
return RE.editor.innerText;
}
RE.setBaseTextColor = function(color) {
RE.editor.style.color = color;
}
RE.setBaseFontSize = function(size) {
RE.editor.style.fontSize = size;
}
RE.setPadding = function(left, top, right, bottom) {
RE.editor.style.paddingLeft = left;
RE.editor.style.paddingTop = top;
RE.editor.style.paddingRight = right;
RE.editor.style.paddingBottom = bottom;
}
RE.setBackgroundColor = function(color) {
document.body.style.backgroundColor = color;
}
RE.setBackgroundImage = function(image) {
RE.editor.style.backgroundImage = image;
}
RE.setWidth = function(size) {
RE.editor.style.minWidth = size;
}
RE.setHeight = function(size) {
RE.editor.style.height = size;
}
RE.setTextAlign = function(align) {
RE.editor.style.textAlign = align;
}
RE.setVerticalAlign = function(align) {
RE.editor.style.verticalAlign = align;
}
RE.setPlaceholder = function(placeholder) {
RE.editor.setAttribute("placeholder", placeholder);
}
RE.setEditorFocus = function() {
RE.editor.focus();
}
RE.setInputEnabled = function(inputEnabled) {
RE.editor.contentEditable = String(inputEnabled);
}
RE.formatBlock = function() {
document.execCommand('formatBlock', false, 'p');
}
RE.undo = function() {
document.execCommand('undo', false, null);
}
RE.redo = function() {
document.execCommand('redo', false, null);
}
RE.setBold = function() {
document.execCommand('bold', false, null);
}
RE.setItalic = function() {
document.execCommand('italic', false, null);
}
RE.setSubscript = function() {
document.execCommand('subscript', false, null);
}
RE.setSuperscript = function() {
document.execCommand('superscript', false, null);
}
RE.setStrikeThrough = function() {
document.execCommand('strikeThrough', false, null);
}
RE.setUnderline = function() {
document.execCommand('underline', false, null);
}
RE.setBullets = function() {
document.execCommand('insertUnorderedList', false, null);
}
RE.setNumbers = function() {
document.execCommand('insertOrderedList', false, null);
}
RE.setTextColor = function(color) {
RE.restorerange();
document.execCommand("styleWithCSS", null, true);
document.execCommand('foreColor', false, color);
document.execCommand("styleWithCSS", null, false);
}
RE.setTextBackgroundColor = function(color) {
RE.restorerange();
document.execCommand("styleWithCSS", null, true);
document.execCommand('hiliteColor', false, color);
document.execCommand("styleWithCSS", null, false);
}
RE.setFontSize = function(fontSize){
document.execCommand("fontSize", false, fontSize);
}
RE.setHeading = function(heading) {
document.execCommand('formatBlock', false, '<h'+heading+'>');
RE.sendElementNameToNative()
}
RE.setIndent = function() {
document.execCommand('indent', false, null);
}
RE.setOutdent = function() {
document.execCommand('outdent', false, null);
}
RE.setJustifyLeft = function() {
document.execCommand('justifyLeft', false, null);
}
RE.setJustifyCenter = function() {
document.execCommand('justifyCenter', false, null);
}
RE.setJustifyRight = function() {
document.execCommand('justifyRight', false, null);
}
RE.setBlockquote = function() {
document.execCommand('formatBlock', false, '<blockquote>');
// var blockId = window.getSelection().focusNode.parentNode;
// $(blockId).addClass("haloBlock")
RE.sendElementNameToNative()
}
RE.insertImage = function(url) {
var html = "<div><img src =\"" + url + "\" style=\" max-width: 100%; display:block; margin:15px auto; height: auto;\"></div><br>"
RE.insertHTML(html);
}
// 替换成缩略图
RE.replaceTbImage = function(imgRuleFlag, gifRuleFlag) {
var imgs = document.getElementsByTagName("img");
var index = 0
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var imageClassName = img.className;
// console.log(imageClassName)
if (imageClassName == "image-link" || img.className == "poster") continue;
if(img.src.indexOf("?") > 0) continue;
// console.log(i)
var tbImg
if(img.src.indexOf(".gif") > 0) {
tbImg = img.src + gifRuleFlag
} else {
tbImg = img.src + imgRuleFlag
}
img.style.cssText = "max-width: 60%; display:block; margin:15px auto; height: auto;"
img.src = tbImg;
if (index == 0) {
var bigImg = document.createElement('img');
bigImg.src = "file:///android_asset/web_load_dfimg_icon.png";
bigImg.style.cssText = "max-width: 20%; margin:15px 0 0 0; height: auto;"
img.parentNode.insertBefore(bigImg, img.parentNode.childNodes[0]);
i++;
if(img.parentNode != null) {
img.parentNode.style.cssText += "text-align: left;"
}
if(img.parentNode != null && img.parentNode.parentNode != null) {
img.parentNode.parentNode.style.cssText += "text-align: left;"
}
}
index ++;
}
}
// 替换成默认图
RE.replaceAllDfImage = function(imgRuleFlag, gifRuleFlag) {
var imgs = document.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var imageClassName = img.className;
if (imageClassName == "image-link" || img.className == "poster") continue;
if(img.src.indexOf("web_load_dfimg_icon") > 0) {
img.parentNode.removeChild(img.parentNode.childNodes[0]);
i--;
} else {
if(img.src.indexOf(".gif") > 0) {
img.src = img.src.split("?")[0] + gifRuleFlag
} else {
img.src = img.src.split("?")[0] + imgRuleFlag
}
}
}
}
// 去除显示大图
RE.hideShowBigPic = function() {
var imgs = document.getElementsByTagName("img");
var j = 0;
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var imageClassName = img.className;
if (imageClassName == "image-link" || img.className == "poster") continue;
if (img.src.indexOf(".gif") == -1) {
j++;
}
}
// 去除显示大图
if (j == 0) {
var imgs = document.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var imageClassName = img.className;
if (imageClassName == "image-link" || img.className == "poster") continue;
if(img.src.indexOf("web_load_dfimg_icon") > 0) {
img.parentNode.removeChild(img.parentNode.childNodes[0]);
break;
}
}
}
}
RE.replaceDfImageByUrl = function(imgUrl, imgRuleFlag, gifRuleFlag) {
var imgs = document.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var imageClassName = img.className;
if (imageClassName == "image-link" || img.className == "poster") continue;
if (img.src.indexOf(imgUrl) != -1) {
if(img.src.indexOf(".gif") > 0) {
img.src = img.src.split("?")[0] + gifRuleFlag
} else {
img.src = img.src.split("?")[0] + imgRuleFlag
}
}
}
RE.hideShowBigPic();
}
RE.ImageClickListener = function() {
var imgs = document.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var imageClassName = img.className;
if (imageClassName == "image-link"|| img.className == "poster") continue;
window.imagelistener.imageArr(img.src);
img.onclick = function() {
window.imagelistener.imageClick(this.src);
}
}
}
RE.insertHTML = function(html) {
RE.restorerange();
document.execCommand('insertHTML', false, html);
}
RE.insertLink = function(url, title) {
RE.restorerange();
var sel = document.getSelection();
if (sel.toString().length == 0) {
document.execCommand("insertHTML",false,"<a href='"+url+"'>"+title+"</a>");
} else if (sel.rangeCount) {
var el = document.createElement("a");
el.setAttribute("href", url);
el.setAttribute("title", title);
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(el);
sel.removeAllRanges();
sel.addRange(range);
}
RE.callback();
}
RE.setTodo = function(text) {
var html = '<input type="checkbox" name="'+ text +'" value="'+ text +'"/> &nbsp;';
document.execCommand('insertHTML', false, html);
}
RE.prepareInsert = function() {
RE.backuprange();
}
RE.backuprange = function(){
var selection = window.getSelection();
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
RE.currentSelection = {
"startContainer": range.startContainer,
"startOffset": range.startOffset,
"endContainer": range.endContainer,
"endOffset": range.endOffset};
}
}
RE.restorerange = function(){
try {
var selection = window.getSelection();
selection.removeAllRanges();
var range = document.createRange();
range.setStart(RE.currentSelection.startContainer, RE.currentSelection.startOffset);
range.setEnd(RE.currentSelection.endContainer, RE.currentSelection.endOffset);
selection.addRange(range);
} catch(error) {
}
}
RE.enabledEditingItems = function(e) {
var items = [];
if (document.queryCommandState('bold')) {
items.push('bold');
}
if (document.queryCommandState('italic')) {
items.push('italic');
}
if (document.queryCommandState('subscript')) {
items.push('subscript');
}
if (document.queryCommandState('superscript')) {
items.push('superscript');
}
if (document.queryCommandState('strikeThrough')) {
items.push('strikeThrough');
}
if (document.queryCommandState('underline')) {
items.push('underline');
}
if (document.queryCommandState('insertOrderedList')) {
items.push('orderedList');
}
if (document.queryCommandState('insertUnorderedList')) {
items.push('unorderedList');
}
if (document.queryCommandState('justifyCenter')) {
items.push('justifyCenter');
}
if (document.queryCommandState('justifyFull')) {
items.push('justifyFull');
}
if (document.queryCommandState('justifyLeft')) {
items.push('justifyLeft');
}
if (document.queryCommandState('justifyRight')) {
items.push('justifyRight');
}
if (document.queryCommandState('insertHorizontalRule')) {
items.push('horizontalRule');
}
var formatBlock = document.queryCommandValue('formatBlock');
if (formatBlock.length > 0) {
items.push(formatBlock);
}
window.location.href = "re-state://" + encodeURI(items.join(','));
}
RE.focus = function() {
var range = document.createRange();
range.selectNodeContents(RE.editor);
range.collapse(false);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
RE.editor.focus();
}
RE.blurFocus = function() {
RE.editor.blur();
}
RE.removeFormat = function() {
document.execCommand('removeFormat', false, null);
}
RE.insertCustomStyleLink = function(data) {
var entity = JSON.parse(data)
var html = "<br/><div class='"+ entity.type +"-container'>\n" +
" <a class='"+ entity.type +"' href=\"javascript:void(0);\" contenteditable=\"false\" onclick=\"customLinkgo(this)\" data-datas='"+ data +"'>\n" +
" <div class='flex-container'>\n" +
" <div class='gh-internal-content img-left'>\n" +
" <img class = \"image-link\" src='"+ entity.icon +"' />\n" +
" </div>\n" +
" <div class='gh-internal-content content-right'>\n" +
" <p class='content-title'>"+ entity.title +"</p>\n" +
" <p class='contents'>"+ entity.brief +"</p>\n" +
" </div>\n" +
" </div>\n" +
" </a>\n" +
" </div><br/>"
var tags = "", gameHtml = ""
if (entity.tags != null) {
for (var i = 0; i < entity.tags.length; i++) {
tags += "<label>"+ entity.tags[i]+"</label>"
}
gameHtml = "<br/><div class='"+ entity.type +"-container'>\n" +
" <a class='"+ entity.type +"' href=\"javascript:void(0);\" contenteditable=\"false\" onclick=\"customLinkgo(this)\" data-datas='"+ data +"'>\n" +
" <div class='flex-container'>\n" +
" <div class='gh-internal-content img-left'>\n" +
" <img class='image-link' src='"+ entity.icon +"' />\n" +
" </div>\n" +
" <div class='gh-internal-content content-right'>\n" +
" <p class='content-title'>"+ entity.title +"</p>\n" +
" <p class='tags'>"+ tags +"</p>\n" +
" </div>\n" +
" </div>\n" +
" </a></div><br/>"
}
switch(entity.type) {
case "answer":
document.execCommand("insertHTML",false, html);
break
case "community_article":
document.execCommand("insertHTML",false, html);
break
case "game":
document.execCommand("insertHTML",false, gameHtml);
break
}
RE.callback();
}
RE.showLinkStyle = function() {
var answerElement = document.getElementsByClassName("answer-container");
for (var i=0;i<answerElement.length;i+=1){
answerElement[i].style.display = 'inline';
}
var articleElement = document.getElementsByClassName("community_article-container");
for (var i=0;i<articleElement.length;i+=1){
articleElement[i].style.display = 'inline';
}
var gameElement = document.getElementsByClassName("game-container");
for (var i=0;i<gameElement.length;i+=1){
gameElement[i].style.display = 'inline';
}
}
RE.hideLinkStyle = function() {
var answerElement = document.getElementsByClassName("answer-container");
for (var i=0;i<answerElement.length;i+=1){
answerElement[i].style.display = 'none';
}
var articleElement = document.getElementsByClassName("community_article-container");
for (var i=0;i<articleElement.length;i+=1){
articleElement[i].style.display = 'none';
}
var gameElement = document.getElementsByClassName("game-container");
for (var i=0;i<gameElement.length;i+=1){
gameElement[i].style.display = 'none';
}
}
// Event Listeners
RE.editor.addEventListener("input", RE.callback);
RE.editor.addEventListener("keyup", function(e) {
var KEY_LEFT = 37, KEY_RIGHT = 39;
if (e.which == KEY_LEFT || e.which == KEY_RIGHT) {
RE.enabledEditingItems(e);
}
RE.sendElementNameToNative()
});
RE.editor.addEventListener("click", function(e) {
RE.enabledEditingItems
RE.sendElementNameToNative()
var s = document.getSelection()
var isNeedRemoveR = RE.recursion(e.target)
if (isNeedRemoveR && s.rangeCount) {
s.removeAllRanges()
}
});
document.addEventListener("selectionchange", function(e) {
RE.sendElementNameToNative()
});
RE.recursion = function(dom) {
var parenDom = dom.parentElement
if (parenDom && parenDom instanceof Element &&
typeClassList.indexOf(parenDom.className) > -1) {
return parenDom
} else if(parenDom && parenDom instanceof Element &&
typeClassList.indexOf(parenDom.className) === -1 && parenDom.nodeName !== 'BODY') {
return RE.recursion(parenDom)
} else {
return null
}
}
// 返回组件标签 多个标签以"空格"划分
RE.sendElementNameToNative = function() {
if (window.getSelection) {
var selection = window.getSelection()
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
var container = range.startContainer;
var elements = " " + container.localName + " ";
var parentElement;
while(true) {
if(parentElement != null) {
parentElement = parentElement.parentElement
} else {
parentElement = container.parentElement
}
if (parentElement == null || parentElement.localName == null) {
break;
}
elements = elements + " " + parentElement.localName + " "
}
// console.log(elements)
window.OnCursorChangeListener.onElements(elements);
}
}
}
// android function to open link
function customLinkgo(self) {
var datas = self.dataset.datas
// console.log(datas)
window.OnLinkClickListener.onClick(datas)
}
// 在web页面播放视频
//RE.initArticleVideo = function(){
// initArticleVideo()
//}
function showNativeDialog(title, message, positive, negative, callback) {
var jsCallbackCode = "(" + function (v) {
window.onNativeDialogCallback(v);
delete window.onNativeDialogCallback;
}.toString() + ")";
window.onNativeDialogCallback = callback;
window.NativeCallBack.showDialog(title, message, positive, negative, jsCallbackCode);
}

View File

@ -1,43 +0,0 @@
/**
* Copyright (C) 2017 Wasabeef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@charset "UTF-8";
html {
height: 100%;
}
body {
overflow: scroll;
display: table;
table-layout: fixed;
width: 100%;
min-height:100%;
}
#editor {
display: table-cell;
outline: 0px solid transparent;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
#editor[placeholder]:empty:not(:focus):before {
content: attr(placeholder);
opacity: .5;
}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,699 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.support.v4.util.Pools;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static android.support.v7.widget.RecyclerView.ViewHolder;
/**
* Helper class that can enqueue and process adapter update operations.
* <p>
* To support animations, RecyclerView presents an older version the Adapter to best represent
* previous state of the layout. Sometimes, this is not trivial when items are removed that were
* not laid out, in which case, RecyclerView has no way of providing that item's view for
* animations.
* <p>
* AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
* pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
* and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
* according to previously deferred operation and dispatch them before the first layout pass. It
* also takes care of updating deferred UpdateOps since order of operations is changed by this
* process.
* <p>
* Although operations may be forwarded to LayoutManager in different orders, resulting data set
* is guaranteed to be the consistent.
*/
class AdapterHelper implements OpReorderer.Callback {
final static int POSITION_TYPE_INVISIBLE = 0;
final static int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
private static final boolean DEBUG = false;
private static final String TAG = "AHT";
private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
final Callback mCallback;
Runnable mOnItemProcessedCallback;
final boolean mDisableRecycler;
final OpReorderer mOpReorderer;
AdapterHelper(Callback callback) {
this(callback, false);
}
AdapterHelper(Callback callback, boolean disableRecycler) {
mCallback = callback;
mDisableRecycler = disableRecycler;
mOpReorderer = new OpReorderer(this);
}
AdapterHelper addUpdateOp(UpdateOp... ops) {
Collections.addAll(mPendingUpdates, ops);
return this;
}
void reset() {
recycleUpdateOpsAndClearList(mPendingUpdates);
recycleUpdateOpsAndClearList(mPostponedList);
}
void preProcess() {
mOpReorderer.reorderOps(mPendingUpdates);
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
applyAdd(op);
break;
case UpdateOp.REMOVE:
applyRemove(op);
break;
case UpdateOp.UPDATE:
applyUpdate(op);
break;
case UpdateOp.MOVE:
applyMove(op);
break;
}
if (mOnItemProcessedCallback != null) {
mOnItemProcessedCallback.run();
}
}
mPendingUpdates.clear();
}
void consumePostponedUpdates() {
final int count = mPostponedList.size();
for (int i = 0; i < count; i++) {
mCallback.onDispatchSecondPass(mPostponedList.get(i));
}
recycleUpdateOpsAndClearList(mPostponedList);
}
private void applyMove(UpdateOp op) {
// MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
// otherwise, it would be converted into a REMOVE operation
postponeAndUpdateViewHolders(op);
}
private void applyRemove(UpdateOp op) {
int tmpStart = op.positionStart;
int tmpCount = 0;
int tmpEnd = op.positionStart + op.itemCount;
int type = -1;
for (int position = op.positionStart; position < tmpEnd; position++) {
boolean typeChanged = false;
ViewHolder vh = mCallback.findViewHolder(position);
if (vh != null || canFindInPreLayout(position)) {
// If a ViewHolder exists or this is a newly added item, we can defer this update
// to post layout stage.
// * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
// * For items that are added and removed in the same process cycle, they won't
// have any effect in pre-layout since their add ops are already deferred to
// post-layout pass.
if (type == POSITION_TYPE_INVISIBLE) {
// Looks like we have other updates that we cannot merge with this one.
// Create an UpdateOp and dispatch it to LayoutManager.
UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
dispatchAndUpdateViewHolders(newOp);
typeChanged = true;
}
type = POSITION_TYPE_NEW_OR_LAID_OUT;
} else {
// This update cannot be recovered because we don't have a ViewHolder representing
// this position. Instead, post it to LayoutManager immediately
if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
// Looks like we have other updates that we cannot merge with this one.
// Create UpdateOp op and dispatch it to LayoutManager.
UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
postponeAndUpdateViewHolders(newOp);
typeChanged = true;
}
type = POSITION_TYPE_INVISIBLE;
}
if (typeChanged) {
position -= tmpCount; // also equal to tmpStart
tmpEnd -= tmpCount;
tmpCount = 1;
} else {
tmpCount++;
}
}
if (tmpCount != op.itemCount) { // all 1 effect
recycleUpdateOp(op);
op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
}
if (type == POSITION_TYPE_INVISIBLE) {
dispatchAndUpdateViewHolders(op);
} else {
postponeAndUpdateViewHolders(op);
}
}
private void applyUpdate(UpdateOp op) {
int tmpStart = op.positionStart;
int tmpCount = 0;
int tmpEnd = op.positionStart + op.itemCount;
int type = -1;
for (int position = op.positionStart; position < tmpEnd; position++) {
ViewHolder vh = mCallback.findViewHolder(position);
if (vh != null || canFindInPreLayout(position)) { // deferred
if (type == POSITION_TYPE_INVISIBLE) {
UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
dispatchAndUpdateViewHolders(newOp);
tmpCount = 0;
tmpStart = position;
}
type = POSITION_TYPE_NEW_OR_LAID_OUT;
} else { // applied
if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
postponeAndUpdateViewHolders(newOp);
tmpCount = 0;
tmpStart = position;
}
type = POSITION_TYPE_INVISIBLE;
}
tmpCount++;
}
if (tmpCount != op.itemCount) { // all 1 effect
recycleUpdateOp(op);
op = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
}
if (type == POSITION_TYPE_INVISIBLE) {
dispatchAndUpdateViewHolders(op);
} else {
postponeAndUpdateViewHolders(op);
}
}
private void dispatchAndUpdateViewHolders(UpdateOp op) {
// tricky part.
// traverse all postpones and revert their changes on this op if necessary, apply updated
// dispatch to them since now they are after this op.
if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
throw new IllegalArgumentException("should not dispatch add or move for pre layout");
}
if (DEBUG) {
Log.d(TAG, "dispatch (pre)" + op);
Log.d(TAG, "postponed state before:");
for (UpdateOp updateOp : mPostponedList) {
Log.d(TAG, updateOp.toString());
}
Log.d(TAG, "----");
}
// handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
// TODO Since move ops are pushed to end, we should not need this anymore
int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
if (DEBUG) {
Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
}
int tmpCnt = 1;
int offsetPositionForPartial = op.positionStart;
final int positionMultiplier;
switch (op.cmd) {
case UpdateOp.UPDATE:
positionMultiplier = 1;
break;
case UpdateOp.REMOVE:
positionMultiplier = 0;
break;
default:
throw new IllegalArgumentException("op should be remove or update." + op);
}
for (int p = 1; p < op.itemCount; p++) {
final int pos = op.positionStart + (positionMultiplier * p);
int updatedPos = updatePositionWithPostponed(pos, op.cmd);
if (DEBUG) {
Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
}
boolean continuous = false;
switch (op.cmd) {
case UpdateOp.UPDATE:
continuous = updatedPos == tmpStart + 1;
break;
case UpdateOp.REMOVE:
continuous = updatedPos == tmpStart;
break;
}
if (continuous) {
tmpCnt++;
} else {
// need to dispatch this separately
UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
if (DEBUG) {
Log.d(TAG, "need to dispatch separately " + tmp);
}
dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
recycleUpdateOp(tmp);
if (op.cmd == UpdateOp.UPDATE) {
offsetPositionForPartial += tmpCnt;
}
tmpStart = updatedPos;// need to remove previously dispatched
tmpCnt = 1;
}
}
recycleUpdateOp(op);
if (tmpCnt > 0) {
UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
if (DEBUG) {
Log.d(TAG, "dispatching:" + tmp);
}
dispatchFirstPassAndUpdateViewHolders(tmp, offsetPositionForPartial);
recycleUpdateOp(tmp);
}
if (DEBUG) {
Log.d(TAG, "post dispatch");
Log.d(TAG, "postponed state after:");
for (UpdateOp updateOp : mPostponedList) {
Log.d(TAG, updateOp.toString());
}
Log.d(TAG, "----");
}
}
void dispatchFirstPassAndUpdateViewHolders(UpdateOp op, int offsetStart) {
mCallback.onDispatchFirstPass(op);
switch (op.cmd) {
case UpdateOp.REMOVE:
mCallback.offsetPositionsForRemovingInvisible(offsetStart, op.itemCount);
break;
case UpdateOp.UPDATE:
mCallback.markViewHoldersUpdated(offsetStart, op.itemCount);
break;
default:
throw new IllegalArgumentException("only remove and update ops can be dispatched"
+ " in first pass");
}
}
private int updatePositionWithPostponed(int pos, int cmd) {
final int count = mPostponedList.size();
for (int i = count - 1; i >= 0; i--) {
UpdateOp postponed = mPostponedList.get(i);
if (postponed.cmd == UpdateOp.MOVE) {
int start, end;
if (postponed.positionStart < postponed.itemCount) {
start = postponed.positionStart;
end = postponed.itemCount;
} else {
start = postponed.itemCount;
end = postponed.positionStart;
}
if (pos >= start && pos <= end) {
//i'm affected
if (start == postponed.positionStart) {
if (cmd == UpdateOp.ADD) {
postponed.itemCount++;
} else if (cmd == UpdateOp.REMOVE) {
postponed.itemCount--;
}
// op moved to left, move it right to revert
pos++;
} else {
if (cmd == UpdateOp.ADD) {
postponed.positionStart++;
} else if (cmd == UpdateOp.REMOVE) {
postponed.positionStart--;
}
// op was moved right, move left to revert
pos--;
}
} else if (pos < postponed.positionStart) {
// postponed MV is outside the dispatched OP. if it is before, offset
if (cmd == UpdateOp.ADD) {
postponed.positionStart++;
postponed.itemCount++;
} else if (cmd == UpdateOp.REMOVE) {
postponed.positionStart--;
postponed.itemCount--;
}
}
} else {
if (postponed.positionStart <= pos) {
if (postponed.cmd == UpdateOp.ADD) {
pos -= postponed.itemCount;
} else if (postponed.cmd == UpdateOp.REMOVE) {
pos += postponed.itemCount;
}
} else {
if (cmd == UpdateOp.ADD) {
postponed.positionStart++;
} else if (cmd == UpdateOp.REMOVE) {
postponed.positionStart--;
}
}
}
if (DEBUG) {
Log.d(TAG, "dispath (step" + i + ")");
Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
for (UpdateOp updateOp : mPostponedList) {
Log.d(TAG, updateOp.toString());
}
Log.d(TAG, "----");
}
}
for (int i = mPostponedList.size() - 1; i >= 0; i--) {
UpdateOp op = mPostponedList.get(i);
if (op.cmd == UpdateOp.MOVE) {
if (op.itemCount == op.positionStart || op.itemCount < 0) {
mPostponedList.remove(i);
recycleUpdateOp(op);
}
} else if (op.itemCount <= 0) {
mPostponedList.remove(i);
recycleUpdateOp(op);
}
}
return pos;
}
private boolean canFindInPreLayout(int position) {
final int count = mPostponedList.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPostponedList.get(i);
if (op.cmd == UpdateOp.MOVE) {
if (findPositionOffset(op.itemCount, i + 1) == position) {
return true;
}
} else if (op.cmd == UpdateOp.ADD) {
// TODO optimize.
final int end = op.positionStart + op.itemCount;
for (int pos = op.positionStart; pos < end; pos++) {
if (findPositionOffset(pos, i + 1) == position) {
return true;
}
}
}
}
return false;
}
private void applyAdd(UpdateOp op) {
postponeAndUpdateViewHolders(op);
}
private void postponeAndUpdateViewHolders(UpdateOp op) {
if (DEBUG) {
Log.d(TAG, "postponing " + op);
}
// Utils.log("add UpdateOp to PostponedList");
mPostponedList.add(op);
// Utils.log("op" + op.positionStart + "=" + op.itemCount);
switch (op.cmd) {
case UpdateOp.ADD:
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
break;
case UpdateOp.MOVE:
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
break;
case UpdateOp.REMOVE:
mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
op.itemCount);
break;
case UpdateOp.UPDATE:
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
break;
default:
throw new IllegalArgumentException("Unknown update op type for " + op);
}
}
boolean hasPendingUpdates() {
return mPendingUpdates.size() > 0;
}
int findPositionOffset(int position) {
return findPositionOffset(position, 0);
}
int findPositionOffset(int position, int firstPostponedItem) {
int count = mPostponedList.size();
for (int i = firstPostponedItem; i < count; ++i) {
UpdateOp op = mPostponedList.get(i);
if (op.cmd == UpdateOp.MOVE) {
if (op.positionStart == position) {
position = op.itemCount;
} else {
if (op.positionStart < position) {
position--; // like a remove
}
if (op.itemCount <= position) {
position++; // like an add
}
}
} else if (op.positionStart <= position) {
if (op.cmd == UpdateOp.REMOVE) {
if (position < op.positionStart + op.itemCount) {
return -1;
}
position -= op.itemCount;
} else if (op.cmd == UpdateOp.ADD) {
position += op.itemCount;
}
}
}
return position;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeChanged(int positionStart, int itemCount) {
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeInserted(int positionStart, int itemCount) {
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeRemoved(int positionStart, int itemCount) {
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
return mPendingUpdates.size() == 1;
}
/**
* @return True if updates should be processed.
*/
boolean onItemRangeMoved(int from, int to, int itemCount) {
if (from == to) {
return false;//no-op
}
if (itemCount != 1) {
throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
}
mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to));
return mPendingUpdates.size() == 1;
}
/**
* Skips pre-processing and applies all updates in one pass.
*/
void consumeUpdatesInOnePass() {
// we still consume postponed updates (if there is) in case there was a pre-process call
// w/o a matching consumePostponedUpdates.
consumePostponedUpdates();
final int count = mPendingUpdates.size();
for (int i = 0; i < count; i++) {
UpdateOp op = mPendingUpdates.get(i);
switch (op.cmd) {
case UpdateOp.ADD:
mCallback.onDispatchSecondPass(op);
mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
break;
case UpdateOp.REMOVE:
mCallback.onDispatchSecondPass(op);
mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
break;
case UpdateOp.UPDATE:
mCallback.onDispatchSecondPass(op);
mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
break;
case UpdateOp.MOVE:
mCallback.onDispatchSecondPass(op);
mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
break;
}
if (mOnItemProcessedCallback != null) {
mOnItemProcessedCallback.run();
}
}
recycleUpdateOpsAndClearList(mPendingUpdates);
}
/**
* Queued operation to happen when child views are updated.
*/
static class UpdateOp {
static final int ADD = 0;
static final int REMOVE = 1;
static final int UPDATE = 2;
static final int MOVE = 3;
static final int POOL_SIZE = 30;
int cmd;
int positionStart;
// holds the target position if this is a MOVE
int itemCount;
UpdateOp(int cmd, int positionStart, int itemCount) {
this.cmd = cmd;
this.positionStart = positionStart;
this.itemCount = itemCount;
}
String cmdToString() {
switch (cmd) {
case ADD:
return "add";
case REMOVE:
return "rm";
case UPDATE:
return "up";
case MOVE:
return "mv";
}
return "??";
}
@Override
public String toString() {
return "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UpdateOp op = (UpdateOp) o;
if (cmd != op.cmd) {
return false;
}
if (cmd == MOVE && Math.abs(itemCount - positionStart) == 1) {
// reverse of this is also true
if (itemCount == op.positionStart && positionStart == op.itemCount) {
return true;
}
}
if (itemCount != op.itemCount) {
return false;
}
if (positionStart != op.positionStart) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = cmd;
result = 31 * result + positionStart;
result = 31 * result + itemCount;
return result;
}
}
@Override
public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
UpdateOp op = mUpdateOpPool.acquire();
if (op == null) {
op = new UpdateOp(cmd, positionStart, itemCount);
} else {
op.cmd = cmd;
op.positionStart = positionStart;
op.itemCount = itemCount;
}
return op;
}
@Override
public void recycleUpdateOp(UpdateOp op) {
if (!mDisableRecycler) {
mUpdateOpPool.release(op);
}
}
void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
final int count = ops.size();
for (int i = 0; i < count; i++) {
recycleUpdateOp(ops.get(i));
}
ops.clear();
}
/**
* Contract between AdapterHelper and RecyclerView.
*/
static interface Callback {
ViewHolder findViewHolder(int position);
void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
void markViewHoldersUpdated(int positionStart, int itemCount);
void onDispatchFirstPass(UpdateOp updateOp);
void onDispatchSecondPass(UpdateOp updateOp);
void offsetPositionsForAdd(int positionStart, int itemCount);
void offsetPositionsForMove(int from, int to);
}
}

View File

@ -0,0 +1,484 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class to manage children.
* <p>
* It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods
* provided by this class. <b>Regular</b> methods are the ones that replicate ViewGroup methods
* like getChildAt, getChildCount etc. These methods ignore hidden children.
* <p>
* When RecyclerView needs direct access to the view group children, it can call unfiltered
* methods like get getUnfilteredChildCount or getUnfilteredChildAt.
*/
class ChildHelper {
private static final boolean DEBUG = false;
private static final String TAG = "ChildrenHelper";
final Callback mCallback;
final Bucket mBucket;
final List<View> mHiddenViews;
ChildHelper(Callback callback) {
mCallback = callback;
mBucket = new Bucket();
mHiddenViews = new ArrayList<View>();
}
/**
* Adds a view to the ViewGroup
*
* @param child View to add.
* @param hidden If set to true, this item will be invisible from regular methods.
*/
void addView(View child, boolean hidden) {
addView(child, -1, hidden);
}
/**
* Add a view to the ViewGroup at an index
*
* @param child View to add.
* @param index Index of the child from the regular perspective (excluding hidden views).
* ChildHelper offsets this index to actual ViewGroup index.
* @param hidden If set to true, this item will be invisible from regular methods.
*/
void addView(View child, int index, boolean hidden) {
final int offset;
if (index < 0) {
offset = mCallback.getChildCount();
} else {
offset = getOffset(index);
}
mCallback.addView(child, offset);
mBucket.insert(offset, hidden);
if (hidden) {
mHiddenViews.add(child);
}
if (DEBUG) {
Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
}
}
private int getOffset(int index) {
if (index < 0) {
return -1; //anything below 0 won't work as diff will be undefined.
}
final int limit = mCallback.getChildCount();
int offset = index;
while (offset < limit) {
final int removedBefore = mBucket.countOnesBefore(offset);
final int diff = index - (offset - removedBefore);
if (diff == 0) {
while (mBucket.get(offset)) { // ensure this offset is not hidden
offset ++;
}
return offset;
} else {
offset += diff;
}
}
return -1;
}
/**
* Removes the provided View from underlying RecyclerView.
*
* @param view The view to remove.
*/
void removeView(View view) {
int index = mCallback.indexOfChild(view);
if (index < 0) {
return;
}
mCallback.removeViewAt(index);
if (mBucket.remove(index)) {
mHiddenViews.remove(view);
}
if (DEBUG) {
Log.d(TAG, "remove View off:" + index + "," + this);
}
}
/**
* Removes the view at the provided index from RecyclerView.
*
* @param index Index of the child from the regular perspective (excluding hidden views).
* ChildHelper offsets this index to actual ViewGroup index.
*/
void removeViewAt(int index) {
final int offset = getOffset(index);
final View view = mCallback.getChildAt(offset);
if (view == null) {
return;
}
mCallback.removeViewAt(offset);
if (mBucket.remove(offset)) {
mHiddenViews.remove(view);
}
if (DEBUG) {
Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
}
}
/**
* Returns the child at provided index.
*
* @param index Index of the child to return in regular perspective.
*/
View getChildAt(int index) {
final int offset = getOffset(index);
return mCallback.getChildAt(offset);
}
/**
* Removes all views from the ViewGroup including the hidden ones.
*/
void removeAllViewsUnfiltered() {
mCallback.removeAllViews();
mBucket.reset();
mHiddenViews.clear();
if (DEBUG) {
Log.d(TAG, "removeAllViewsUnfiltered");
}
}
/**
* This can be used to find a disappearing view by position.
*
* @param position The adapter position of the item.
* @param type View type, can be {@link RecyclerView#INVALID_TYPE}.
* @return A hidden view with a valid ViewHolder that matches the position and type.
*/
View findHiddenNonRemovedView(int position, int type) {
final int count = mHiddenViews.size();
for (int i = 0; i < count; i++) {
final View view = mHiddenViews.get(i);
RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
if (holder.getPosition() == position && !holder.isInvalid() &&
(type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) {
return view;
}
}
return null;
}
/**
* Attaches the provided view to the underlying ViewGroup.
*
* @param child Child to attach.
* @param index Index of the child to attach in regular perspective.
* @param layoutParams LayoutParams for the child.
* @param hidden If set to true, this item will be invisible to the regular methods.
*/
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams,
boolean hidden) {
final int offset;
if (index < 0) {
offset = mCallback.getChildCount();
} else {
offset = getOffset(index);
}
mCallback.attachViewToParent(child, offset, layoutParams);
mBucket.insert(offset, hidden);
if (DEBUG) {
Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
"h:" + hidden + ", " + this);
}
}
/**
* Returns the number of children that are not hidden.
*
* @return Number of children that are not hidden.
* @see #getChildAt(int)
*/
int getChildCount() {
return mCallback.getChildCount() - mHiddenViews.size();
}
/**
* Returns the total number of children.
*
* @return The total number of children including the hidden views.
* @see #getUnfilteredChildAt(int)
*/
int getUnfilteredChildCount() {
return mCallback.getChildCount();
}
/**
* Returns a child by ViewGroup offset. ChildHelper won't offset this index.
*
* @param index ViewGroup index of the child to return.
* @return The view in the provided index.
*/
View getUnfilteredChildAt(int index) {
return mCallback.getChildAt(index);
}
/**
* Detaches the view at the provided index.
*
* @param index Index of the child to return in regular perspective.
*/
void detachViewFromParent(int index) {
final int offset = getOffset(index);
mCallback.detachViewFromParent(offset);
mBucket.remove(offset);
if (DEBUG) {
Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
}
}
/**
* Returns the index of the child in regular perspective.
*
* @param child The child whose index will be returned.
* @return The regular perspective index of the child or -1 if it does not exists.
*/
int indexOfChild(View child) {
final int index = mCallback.indexOfChild(child);
if (index == -1) {
return -1;
}
if (mBucket.get(index)) {
if (DEBUG) {
throw new IllegalArgumentException("cannot get index of a hidden child");
} else {
return -1;
}
}
// reverse the index
return index - mBucket.countOnesBefore(index);
}
/**
* Returns whether a View is visible to LayoutManager or not.
*
* @param view The child view to check. Should be a child of the Callback.
* @return True if the View is not visible to LayoutManager
*/
boolean isHidden(View view) {
return mHiddenViews.contains(view);
}
/**
* Marks a child view as hidden.
*
* @param view The view to hide.
*/
void hide(View view) {
final int offset = mCallback.indexOfChild(view);
if (offset < 0) {
throw new IllegalArgumentException("view is not a child, cannot hide " + view);
}
if (DEBUG && mBucket.get(offset)) {
throw new RuntimeException("trying to hide same view twice, how come ? " + view);
}
mBucket.set(offset);
mHiddenViews.add(view);
if (DEBUG) {
Log.d(TAG, "hiding child " + view + " at offset " + offset+ ", " + this);
}
}
@Override
public String toString() {
return mBucket.toString();
}
/**
* Removes a view from the ViewGroup if it is hidden.
*
* @param view The view to remove.
* @return True if the View is found and it is hidden. False otherwise.
*/
boolean removeViewIfHidden(View view) {
final int index = mCallback.indexOfChild(view);
if (index == -1) {
if (mHiddenViews.remove(view) && DEBUG) {
throw new IllegalStateException("view is in hidden list but not in view group");
}
return true;
}
if (mBucket.get(index)) {
mBucket.remove(index);
mCallback.removeViewAt(index);
if (!mHiddenViews.remove(view) && DEBUG) {
throw new IllegalStateException(
"removed a hidden view but it is not in hidden views list");
}
return true;
}
return false;
}
/**
* Bitset implementation that provides methods to offset indices.
*/
static class Bucket {
final static int BITS_PER_WORD = Long.SIZE;
final static long LAST_BIT = 1L << (Long.SIZE - 1);
long mData = 0;
Bucket next;
void set(int index) {
if (index >= BITS_PER_WORD) {
ensureNext();
next.set(index - BITS_PER_WORD);
} else {
mData |= 1L << index;
}
}
private void ensureNext() {
if (next == null) {
next = new Bucket();
}
}
void clear(int index) {
if (index >= BITS_PER_WORD) {
if (next != null) {
next.clear(index - BITS_PER_WORD);
}
} else {
mData &= ~(1L << index);
}
}
boolean get(int index) {
if (index >= BITS_PER_WORD) {
ensureNext();
return next.get(index - BITS_PER_WORD);
} else {
return (mData & (1L << index)) != 0;
}
}
void reset() {
mData = 0;
if (next != null) {
next.reset();
}
}
void insert(int index, boolean value) {
if (index >= BITS_PER_WORD) {
ensureNext();
next.insert(index - BITS_PER_WORD, value);
} else {
final boolean lastBit = (mData & LAST_BIT) != 0;
long mask = (1L << index) - 1;
final long before = mData & mask;
final long after = ((mData & ~mask)) << 1;
mData = before | after;
if (value) {
set(index);
} else {
clear(index);
}
if (lastBit || next != null) {
ensureNext();
next.insert(0, lastBit);
}
}
}
boolean remove(int index) {
if (index >= BITS_PER_WORD) {
ensureNext();
return next.remove(index - BITS_PER_WORD);
} else {
long mask = (1L << index);
final boolean value = (mData & mask) != 0;
mData &= ~mask;
mask = mask - 1;
final long before = mData & mask;
// cannot use >> because it adds one.
final long after = Long.rotateRight(mData & ~mask, 1);
mData = before | after;
if (next != null) {
if (next.get(0)) {
set(BITS_PER_WORD - 1);
}
next.remove(0);
}
return value;
}
}
int countOnesBefore(int index) {
if (next == null) {
if (index >= BITS_PER_WORD) {
return Long.bitCount(mData);
}
return Long.bitCount(mData & ((1L << index) - 1));
}
if (index < BITS_PER_WORD) {
return Long.bitCount(mData & ((1L << index) - 1));
} else {
return next.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
}
}
@Override
public String toString() {
return next == null ? Long.toBinaryString(mData)
: next.toString() + "xx" + Long.toBinaryString(mData);
}
}
static interface Callback {
int getChildCount();
void addView(View child, int index);
int indexOfChild(View view);
void removeViewAt(int index);
View getChildAt(int offset);
void removeAllViews();
RecyclerView.ViewHolder getChildViewHolder(View view);
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
void detachViewFromParent(int offset);
}
}

View File

@ -0,0 +1,628 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* This implementation of {@link RecyclerView.ItemAnimator} provides basic
* animations on remove, add, and move events that happen to the items in
* a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
*
* @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
*/
public class DefaultItemAnimator extends RecyclerView.ItemAnimator {
private static final boolean DEBUG = false;
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>();
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>();
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>();
private ArrayList<ArrayList<ViewHolder>> mAdditionsList =
new ArrayList<ArrayList<ViewHolder>>();
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>();
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>();
private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>();
private static class MoveInfo {
public ViewHolder holder;
public int fromX, fromY, toX, toY;
private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
this.holder = holder;
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}
}
private static class ChangeInfo {
public ViewHolder oldHolder, newHolder;
public int fromX, fromY, toX, toY;
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
this.oldHolder = oldHolder;
this.newHolder = newHolder;
}
private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
this(oldHolder, newHolder);
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}
@Override
public String toString() {
return "ChangeInfo{" +
"oldHolder=" + oldHolder +
", newHolder=" + newHolder +
", fromX=" + fromX +
", fromY=" + fromY +
", toX=" + toX +
", toY=" + toY +
'}';
}
}
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
// First, remove stuff
for (ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
if (movesPending) {
final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
moves.addAll(mPendingMoves);
mMovesList.add(moves);
mPendingMoves.clear();
Runnable mover = new Runnable() {
@Override
public void run() {
for (MoveInfo moveInfo : moves) {
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
moves.clear();
mMovesList.remove(moves);
}
};
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else {
mover.run();
}
}
// Next, change stuff, to run in parallel with move animations
if (changesPending) {
final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
changes.addAll(mPendingChanges);
mChangesList.add(changes);
mPendingChanges.clear();
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
changes.clear();
mChangesList.remove(changes);
}
};
if (removalsPending) {
ViewHolder holder = changes.get(0).oldHolder;
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
} else {
changer.run();
}
}
// Next, add stuff
if (additionsPending) {
final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
Runnable adder = new Runnable() {
public void run() {
for (ViewHolder holder : additions) {
animateAddImpl(holder);
}
additions.clear();
mAdditionsList.remove(additions);
}
};
if (removalsPending || movesPending || changesPending) {
long removeDuration = removalsPending ? getRemoveDuration() : 0;
long moveDuration = movesPending ? getMoveDuration() : 0;
long changeDuration = changesPending ? getChangeDuration() : 0;
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
View view = additions.get(0).itemView;
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
} else {
adder.run();
}
}
}
@Override
public boolean animateRemove(final ViewHolder holder) {
endAnimation(holder);
mPendingRemovals.add(holder);
return true;
}
private void animateRemoveImpl(final ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
animation.setDuration(getRemoveDuration())
.alpha(0).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
ViewCompat.setAlpha(view, 1);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
mRemoveAnimations.add(holder);
}
@Override
public boolean animateAdd(final ViewHolder holder) {
endAnimation(holder);
ViewCompat.setAlpha(holder.itemView, 0);
mPendingAdditions.add(holder);
return true;
}
private void animateAddImpl(final ViewHolder holder) {
final View view = holder.itemView;
mAddAnimations.add(holder);
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
animation.alpha(1).setDuration(getAddDuration()).
setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(View view) {
ViewCompat.setAlpha(view, 1);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
int toX, int toY) {
final View view = holder.itemView;
fromX += ViewCompat.getTranslationX(holder.itemView);
fromY += ViewCompat.getTranslationY(holder.itemView);
endAnimation(holder);
int deltaX = toX - fromX;
int deltaY = toY - fromY;
if (deltaX == 0 && deltaY == 0) {
dispatchMoveFinished(holder);
return false;
}
if (deltaX != 0) {
ViewCompat.setTranslationX(view, -deltaX);
}
if (deltaY != 0) {
ViewCompat.setTranslationY(view, -deltaY);
}
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
return true;
}
private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
final View view = holder.itemView;
final int deltaX = toX - fromX;
final int deltaY = toY - fromY;
if (deltaX != 0) {
ViewCompat.animate(view).translationX(0);
}
if (deltaY != 0) {
ViewCompat.animate(view).translationY(0);
}
// TODO: make EndActions end listeners instead, since end actions aren't called when
// vpas are canceled (and can't end them. why?)
// need listener functionality in VPACompat for this. Ick.
mMoveAnimations.add(holder);
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchMoveStarting(holder);
}
@Override
public void onAnimationCancel(View view) {
if (deltaX != 0) {
ViewCompat.setTranslationX(view, 0);
}
if (deltaY != 0) {
ViewCompat.setTranslationY(view, 0);
}
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
dispatchMoveFinished(holder);
mMoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
endAnimation(oldHolder);
int deltaX = (int) (toX - fromX - prevTranslationX);
int deltaY = (int) (toY - fromY - prevTranslationY);
// recover prev translation state after ending animation
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
if (newHolder != null && newHolder.itemView != null) {
// carry over translation values
endAnimation(newHolder);
ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
ViewCompat.setAlpha(newHolder.itemView, 0);
}
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
return true;
}
private void animateChangeImpl(final ChangeInfo changeInfo) {
final ViewHolder holder = changeInfo.oldHolder;
final View view = holder.itemView;
final ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
mChangeAnimations.add(changeInfo.oldHolder);
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
getChangeDuration());
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(changeInfo.oldHolder, true);
}
@Override
public void onAnimationEnd(View view) {
oldViewAnim.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, 0);
ViewCompat.setTranslationY(view, 0);
dispatchChangeFinished(changeInfo.oldHolder, true);
mChangeAnimations.remove(changeInfo.oldHolder);
dispatchFinishedWhenDone();
}
}).start();
if (newView != null) {
mChangeAnimations.add(changeInfo.newHolder);
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
alpha(1).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(changeInfo.newHolder, false);
}
@Override
public void onAnimationEnd(View view) {
newViewAnimation.setListener(null);
ViewCompat.setAlpha(newView, 1);
ViewCompat.setTranslationX(newView, 0);
ViewCompat.setTranslationY(newView, 0);
dispatchChangeFinished(changeInfo.newHolder, false);
mChangeAnimations.remove(changeInfo.newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
}
private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
for (int i = infoList.size() - 1; i >= 0; i--) {
ChangeInfo changeInfo = infoList.get(i);
if (endChangeAnimationIfNecessary(changeInfo, item)) {
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
infoList.remove(changeInfo);
}
}
}
}
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
if (changeInfo.oldHolder != null) {
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
}
if (changeInfo.newHolder != null) {
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
}
}
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
boolean oldItem = false;
if (changeInfo.newHolder == item) {
changeInfo.newHolder = null;
} else if (changeInfo.oldHolder == item) {
changeInfo.oldHolder = null;
oldItem = true;
} else {
return false;
}
ViewCompat.setAlpha(item.itemView, 1);
ViewCompat.setTranslationX(item.itemView, 0);
ViewCompat.setTranslationY(item.itemView, 0);
dispatchChangeFinished(item, oldItem);
return true;
}
@Override
public void endAnimation(ViewHolder item) {
final View view = item.itemView;
// this will trigger end callback which should set properties to their target values.
ViewCompat.animate(view).cancel();
// TODO if some other animations are chained to end, how do we cancel them as well?
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
MoveInfo moveInfo = mPendingMoves.get(i);
if (moveInfo.holder == item) {
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item);
mPendingMoves.remove(item);
}
}
endChangeAnimation(mPendingChanges, item);
if (mPendingRemovals.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchRemoveFinished(item);
}
if (mPendingAdditions.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
}
for (int i = mChangesList.size() - 1; i >= 0; i--) {
ArrayList<ChangeInfo> changes = mChangesList.get(i);
endChangeAnimation(changes, item);
if (changes.isEmpty()) {
mChangesList.remove(changes);
}
}
for (int i = mMovesList.size() - 1; i >= 0; i--) {
ArrayList<MoveInfo> moves = mMovesList.get(i);
for (int j = moves.size() - 1; j >= 0; j--) {
MoveInfo moveInfo = moves.get(j);
if (moveInfo.holder == item) {
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item);
moves.remove(j);
if (moves.isEmpty()) {
mMovesList.remove(moves);
}
break;
}
}
}
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
if (additions.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
mAdditionsList.remove(additions);
}
}
}
// animations should be ended by the cancel above.
if (mRemoveAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mRemoveAnimations list");
}
if (mAddAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mAddAnimations list");
}
if (mChangeAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mChangeAnimations list");
}
if (mMoveAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mMoveAnimations list");
}
dispatchFinishedWhenDone();
}
@Override
public boolean isRunning() {
return (!mPendingAdditions.isEmpty() ||
!mPendingChanges.isEmpty() ||
!mPendingMoves.isEmpty() ||
!mPendingRemovals.isEmpty() ||
!mMoveAnimations.isEmpty() ||
!mRemoveAnimations.isEmpty() ||
!mAddAnimations.isEmpty() ||
!mChangeAnimations.isEmpty() ||
!mMovesList.isEmpty() ||
!mAdditionsList.isEmpty() ||
!mChangesList.isEmpty());
}
/**
* Check the state of currently pending and running animations. If there are none
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
* listeners.
*/
private void dispatchFinishedWhenDone() {
if (!isRunning()) {
dispatchAnimationsFinished();
}
}
@Override
public void endAnimations() {
int count = mPendingMoves.size();
for (int i = count - 1; i >= 0; i--) {
MoveInfo item = mPendingMoves.get(i);
View view = item.holder.itemView;
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item.holder);
mPendingMoves.remove(i);
}
count = mPendingRemovals.size();
for (int i = count - 1; i >= 0; i--) {
ViewHolder item = mPendingRemovals.get(i);
dispatchRemoveFinished(item);
mPendingRemovals.remove(i);
}
count = mPendingAdditions.size();
for (int i = count - 1; i >= 0; i--) {
ViewHolder item = mPendingAdditions.get(i);
View view = item.itemView;
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
mPendingAdditions.remove(i);
}
count = mPendingChanges.size();
for (int i = count - 1; i >= 0; i--) {
endChangeAnimationIfNecessary(mPendingChanges.get(i));
}
mPendingChanges.clear();
if (!isRunning()) {
return;
}
int listCount = mMovesList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<MoveInfo> moves = mMovesList.get(i);
count = moves.size();
for (int j = count - 1; j >= 0; j--) {
MoveInfo moveInfo = moves.get(j);
ViewHolder item = moveInfo.holder;
View view = item.itemView;
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(moveInfo.holder);
moves.remove(j);
if (moves.isEmpty()) {
mMovesList.remove(moves);
}
}
}
listCount = mAdditionsList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<ViewHolder> additions = mAdditionsList.get(i);
count = additions.size();
for (int j = count - 1; j >= 0; j--) {
ViewHolder item = additions.get(j);
View view = item.itemView;
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
additions.remove(j);
if (additions.isEmpty()) {
mAdditionsList.remove(additions);
}
}
}
listCount = mChangesList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<ChangeInfo> changes = mChangesList.get(i);
count = changes.size();
for (int j = count - 1; j >= 0; j--) {
endChangeAnimationIfNecessary(changes.get(j));
if (changes.isEmpty()) {
mChangesList.remove(changes);
}
}
}
cancelAll(mRemoveAnimations);
cancelAll(mMoveAnimations);
cancelAll(mAddAnimations);
cancelAll(mChangeAnimations);
dispatchAnimationsFinished();
}
void cancelAll(List<ViewHolder> viewHolders) {
for (int i = viewHolders.size() - 1; i >= 0; i--) {
ViewCompat.animate(viewHolders.get(i).itemView).cancel();
}
}
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
@Override
public void onAnimationStart(View view) {}
@Override
public void onAnimationEnd(View view) {}
@Override
public void onAnimationCancel(View view) {}
};
}

View File

@ -0,0 +1,816 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific languag`e governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import java.util.Arrays;
/**
* A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.
* <p>
* By default, each item occupies 1 span. You can change it by providing a custom
* {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
*/
public class GridLayoutManager extends LinearLayoutManager {
private static final boolean DEBUG = false;
private static final String TAG = "GridLayoutManager";
public static final int DEFAULT_SPAN_COUNT = -1;
/**
* The measure spec for the scroll direction.
*/
static final int MAIN_DIR_SPEC =
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int mSpanCount = DEFAULT_SPAN_COUNT;
/**
* The size of each span
*/
int mSizePerSpan;
/**
* Temporary array to keep views in layoutChunk method
*/
View[] mSet;
final SparseIntArray mPreLayoutSpanSizeCache = new SparseIntArray();
final SparseIntArray mPreLayoutSpanIndexCache = new SparseIntArray();
SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
// re-used variable to acquire decor insets from RecyclerView
final Rect mDecorInsets = new Rect();
/**
* Creates a vertical GridLayoutManager
*
* @param context Current context, will be used to access resources.
* @param spanCount The number of columns in the grid
*/
public GridLayoutManager(Context context, int spanCount) {
super(context);
setSpanCount(spanCount);
}
/**
* @param context Current context, will be used to access resources.
* @param spanCount The number of columns or rows in the grid
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
* #VERTICAL}.
* @param reverseLayout When set to true, layouts from end to start.
*/
public GridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super(context, orientation, reverseLayout);
setSpanCount(spanCount);
}
/**
* stackFromEnd is not supported by GridLayoutManager. Consider using
* {@link #setReverseLayout(boolean)}.
*/
@Override
public void setStackFromEnd(boolean stackFromEnd) {
if (stackFromEnd) {
throw new UnsupportedOperationException(
"GridLayoutManager does not support stack from end."
+ " Consider using reverse layout");
}
super.setStackFromEnd(false);
}
@Override
public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return mSpanCount;
}
if (state.getItemCount() < 1) {
return 0;
}
return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
}
@Override
public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return mSpanCount;
}
if (state.getItemCount() < 1) {
return 0;
}
return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
}
@Override
public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
ViewGroup.LayoutParams lp = host.getLayoutParams();
if (!(lp instanceof LayoutParams)) {
super.onInitializeAccessibilityNodeInfoForItem(host, info);
return;
}
LayoutParams glp = (LayoutParams) lp;
int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewPosition());
if (mOrientation == HORIZONTAL) {
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
glp.getSpanIndex(), glp.getSpanSize(),
spanGroupIndex, 1,
mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
} else { // VERTICAL
info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
spanGroupIndex , 1,
glp.getSpanIndex(), glp.getSpanSize(),
mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
}
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout()) {
cachePreLayoutSpanMapping();
}
super.onLayoutChildren(recycler, state);
if (DEBUG) {
validateChildOrder();
}
clearPreLayoutSpanMappingCache();
}
private void clearPreLayoutSpanMappingCache() {
mPreLayoutSpanSizeCache.clear();
mPreLayoutSpanIndexCache.clear();
}
private void cachePreLayoutSpanMapping() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
final int viewPosition = lp.getViewPosition();
mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
}
}
@Override
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
mSpanSizeLookup.invalidateSpanIndexCache();
}
@Override
public void onItemsChanged(RecyclerView recyclerView) {
mSpanSizeLookup.invalidateSpanIndexCache();
}
@Override
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
mSpanSizeLookup.invalidateSpanIndexCache();
}
@Override
public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
mSpanSizeLookup.invalidateSpanIndexCache();
}
@Override
public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
mSpanSizeLookup.invalidateSpanIndexCache();
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
return new LayoutParams(c, attrs);
}
@Override
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof ViewGroup.MarginLayoutParams) {
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
} else {
return new LayoutParams(lp);
}
}
@Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
return lp instanceof LayoutParams;
}
/**
* Sets the source to get the number of spans occupied by each item in the adapter.
*
* @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
* occupied by each item
*/
public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
mSpanSizeLookup = spanSizeLookup;
}
/**
* Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
*
* @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
*/
public SpanSizeLookup getSpanSizeLookup() {
return mSpanSizeLookup;
}
private void updateMeasurements() {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
}
mSizePerSpan = totalSpace / mSpanCount;
}
@Override
void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) {
super.onAnchorReady(state, anchorInfo);
updateMeasurements();
if (state.getItemCount() > 0 && !state.isPreLayout()) {
ensureAnchorIsInFirstSpan(anchorInfo);
}
if (mSet == null || mSet.length != mSpanCount) {
mSet = new View[mSpanCount];
}
}
private void ensureAnchorIsInFirstSpan(AnchorInfo anchorInfo) {
int span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
while (span > 0 && anchorInfo.mPosition > 0) {
anchorInfo.mPosition--;
span = mSpanSizeLookup.getCachedSpanIndex(anchorInfo.mPosition, mSpanCount);
}
}
private int getSpanGroupIndex(RecyclerView.Recycler recycler, RecyclerView.State state,
int viewPosition) {
if (!state.isPreLayout()) {
return mSpanSizeLookup.getSpanGroupIndex(viewPosition, mSpanCount);
}
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(viewPosition);
if (adapterPosition == -1) {
if (DEBUG) {
throw new RuntimeException("Cannot find span group index for position "
+ viewPosition);
}
Log.w(TAG, "Cannot find span size for pre layout position. " + viewPosition);
return 0;
}
return mSpanSizeLookup.getSpanGroupIndex(adapterPosition, mSpanCount);
}
private int getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
if (!state.isPreLayout()) {
return mSpanSizeLookup.getCachedSpanIndex(pos, mSpanCount);
}
final int cached = mPreLayoutSpanIndexCache.get(pos, -1);
if (cached != -1) {
return cached;
}
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
if (adapterPosition == -1) {
if (DEBUG) {
throw new RuntimeException("Cannot find span index for pre layout position. It is"
+ " not cached, not in the adapter. Pos:" + pos);
}
Log.w(TAG, "Cannot find span size for pre layout position. It is"
+ " not cached, not in the adapter. Pos:" + pos);
return 0;
}
return mSpanSizeLookup.getCachedSpanIndex(adapterPosition, mSpanCount);
}
private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
if (!state.isPreLayout()) {
return mSpanSizeLookup.getSpanSize(pos);
}
final int cached = mPreLayoutSpanSizeCache.get(pos, -1);
if (cached != -1) {
return cached;
}
final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
if (adapterPosition == -1) {
if (DEBUG) {
throw new RuntimeException("Cannot find span size for pre layout position. It is"
+ " not cached, not in the adapter. Pos:" + pos);
}
Log.w(TAG, "Cannot find span size for pre layout position. It is"
+ " not cached, not in the adapter. Pos:" + pos);
return 1;
}
return mSpanSizeLookup.getSpanSize(adapterPosition);
}
@Override
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
final boolean layingOutInPrimaryDirection =
layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
int count = 0;
int consumedSpanCount = 0;
int remainingSpan = mSpanCount;
if (!layingOutInPrimaryDirection) {
int itemSpanIndex = getSpanIndex(recycler, state, layoutState.mCurrentPosition);
int itemSpanSize = getSpanSize(recycler, state, layoutState.mCurrentPosition);
remainingSpan = itemSpanIndex + itemSpanSize;
}
while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
int pos = layoutState.mCurrentPosition;
final int spanSize = getSpanSize(recycler, state, pos);
if (spanSize > mSpanCount) {
throw new IllegalArgumentException("Item at position " + pos + " requires " +
spanSize + " spans but GridLayoutManager has only " + mSpanCount
+ " spans.");
}
remainingSpan -= spanSize;
if (remainingSpan < 0) {
break; // item did not fit into this row or column
}
View view = layoutState.next(recycler);
if (view == null) {
break;
}
consumedSpanCount += spanSize;
mSet[count] = view;
count++;
}
if (count == 0) {
result.mFinished = true;
return;
}
int maxSize = 0;
// we should assign spans before item decor offsets are calculated
assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
for (int i = 0; i < count; i++) {
View view = mSet[i];
if (layoutState.mScrapList == null) {
if (layingOutInPrimaryDirection) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (layingOutInPrimaryDirection) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
int spanSize = getSpanSize(recycler, state, getPosition(view));
final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
View.MeasureSpec.EXACTLY);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (mOrientation == VERTICAL) {
measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height));
} else {
measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec);
}
final int size = mOrientationHelper.getDecoratedMeasurement(view);
if (size > maxSize) {
maxSize = size;
}
}
// views that did not measure the maxSize has to be re-measured
final int maxMeasureSpec = getMainDirSpec(maxSize);
for (int i = 0; i < count; i ++) {
final View view = mSet[i];
if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
int spanSize = getSpanSize(recycler, state, getPosition(view));
final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
View.MeasureSpec.EXACTLY);
if (mOrientation == VERTICAL) {
measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec);
} else {
measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec);
}
}
}
result.mConsumed = maxSize;
int left = 0, right = 0, top = 0, bottom = 0;
if (mOrientation == VERTICAL) {
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = bottom - maxSize;
} else {
top = layoutState.mOffset;
bottom = top + maxSize;
}
} else {
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = right - maxSize;
} else {
left = layoutState.mOffset;
right = left + maxSize;
}
}
for (int i = 0; i < count; i++) {
View view = mSet[i];
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (mOrientation == VERTICAL) {
left = getPaddingLeft() + mSizePerSpan * params.mSpanIndex;
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
top = getPaddingTop() + mSizePerSpan * params.mSpanIndex;
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
+ ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable |= view.isFocusable();
}
Arrays.fill(mSet, null);
}
private int getMainDirSpec(int dim) {
if (dim < 0) {
return MAIN_DIR_SPEC;
} else {
return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
}
}
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) {
calculateItemDecorationsForChild(child, mDecorInsets);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left,
lp.rightMargin + mDecorInsets.right);
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
lp.bottomMargin + mDecorInsets.bottom);
child.measure(widthSpec, heightSpec);
}
private int updateSpecWithExtra(int spec, int startInset, int endInset) {
if (startInset == 0 && endInset == 0) {
return spec;
}
final int mode = View.MeasureSpec.getMode(spec);
if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
return View.MeasureSpec.makeMeasureSpec(
View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
}
return spec;
}
private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
int consumedSpanCount, boolean layingOutInPrimaryDirection) {
int span, spanDiff, start, end, diff;
// make sure we traverse from min position to max position
if (layingOutInPrimaryDirection) {
start = 0;
end = count;
diff = 1;
} else {
start = count - 1;
end = -1;
diff = -1;
}
if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span
span = consumedSpanCount - 1;
spanDiff = -1;
} else {
span = 0;
spanDiff = 1;
}
for (int i = start; i != end; i += diff) {
View view = mSet[i];
LayoutParams params = (LayoutParams) view.getLayoutParams();
params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
if (spanDiff == -1 && params.mSpanSize > 1) {
params.mSpanIndex = span - (params.mSpanSize - 1);
} else {
params.mSpanIndex = span;
}
span += spanDiff * params.mSpanSize;
}
}
/**
* Returns the number of spans laid out by this grid.
*
* @return The number of spans
* @see #setSpanCount(int)
*/
public int getSpanCount() {
return mSpanCount;
}
/**
* Sets the number of spans to be laid out.
* <p>
* If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
* If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
*
* @param spanCount The total number of spans in the grid
* @see #getSpanCount()
*/
public void setSpanCount(int spanCount) {
if (spanCount == mSpanCount) {
return;
}
if (spanCount < 1) {
throw new IllegalArgumentException("Span count should be at least 1. Provided "
+ spanCount);
}
mSpanCount = spanCount;
mSpanSizeLookup.invalidateSpanIndexCache();
}
/**
* A helper class to provide the number of spans each item occupies.
* <p>
* Default implementation sets each item to occupy exactly 1 span.
*
* @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
*/
public static abstract class SpanSizeLookup {
final SparseIntArray mSpanIndexCache = new SparseIntArray();
private boolean mCacheSpanIndices = false;
/**
* Returns the number of span occupied by the item at <code>position</code>.
*
* @param position The adapter position of the item
* @return The number of spans occupied by the item at the provided position
*/
abstract public int getSpanSize(int position);
/**
* Sets whether the results of {@link #getSpanIndex(int, int)} method should be cached or
* not. By default these values are not cached. If you are not overriding
* {@link #getSpanIndex(int, int)}, you should set this to true for better performance.
*
* @param cacheSpanIndices Whether results of getSpanIndex should be cached or not.
*/
public void setSpanIndexCacheEnabled(boolean cacheSpanIndices) {
mCacheSpanIndices = cacheSpanIndices;
}
/**
* Clears the span index cache. GridLayoutManager automatically calls this method when
* adapter changes occur.
*/
public void invalidateSpanIndexCache() {
mSpanIndexCache.clear();
}
/**
* Returns whether results of {@link #getSpanIndex(int, int)} method are cached or not.
*
* @return True if results of {@link #getSpanIndex(int, int)} are cached.
*/
public boolean isSpanIndexCacheEnabled() {
return mCacheSpanIndices;
}
int getCachedSpanIndex(int position, int spanCount) {
if (!mCacheSpanIndices) {
return getSpanIndex(position, spanCount);
}
final int existing = mSpanIndexCache.get(position, -1);
if (existing != -1) {
return existing;
}
final int value = getSpanIndex(position, spanCount);
mSpanIndexCache.put(position, value);
return value;
}
/**
* Returns the final span index of the provided position.
* <p>
* If you have a faster way to calculate span index for your items, you should override
* this method. Otherwise, you should enable span index cache
* ({@link #setSpanIndexCacheEnabled(boolean)}) for better performance. When caching is
* disabled, default implementation traverses all items from 0 to
* <code>position</code>. When caching is enabled, it calculates from the closest cached
* value before the <code>position</code>.
* <p>
* If you override this method, you need to make sure it is consistent with
* {@link #getSpanSize(int)}. GridLayoutManager does not call this method for
* each item. It is called only for the reference item and rest of the items
* are assigned to spans based on the reference item. For example, you cannot assign a
* position to span 2 while span 1 is empty.
* <p>
* Note that span offsets always start with 0 and are not affected by RTL.
*
* @param position The position of the item
* @param spanCount The total number of spans in the grid
* @return The final span position of the item. Should be between 0 (inclusive) and
* <code>spanCount</code>(exclusive)
*/
public int getSpanIndex(int position, int spanCount) {
int positionSpanSize = getSpanSize(position);
if (positionSpanSize == spanCount) {
return 0; // quick return for full-span items
}
int span = 0;
int startPos = 0;
// If caching is enabled, try to jump
if (mCacheSpanIndices && mSpanIndexCache.size() > 0) {
int prevKey = findReferenceIndexFromCache(position);
if (prevKey >= 0) {
span = mSpanIndexCache.get(prevKey) + getSpanSize(prevKey);
startPos = prevKey + 1;
}
}
for (int i = startPos; i < position; i++) {
int size = getSpanSize(i);
span += size;
if (span == spanCount) {
span = 0;
} else if (span > spanCount) {
// did not fit, moving to next row / column
span = size;
}
}
if (span + positionSpanSize <= spanCount) {
return span;
}
return 0;
}
int findReferenceIndexFromCache(int position) {
int lo = 0;
int hi = mSpanIndexCache.size() - 1;
while (lo <= hi) {
final int mid = (lo + hi) >>> 1;
final int midVal = mSpanIndexCache.keyAt(mid);
if (midVal < position) {
lo = mid + 1;
} else {
hi = mid - 1;
}
}
int index = lo - 1;
if (index >= 0 && index < mSpanIndexCache.size()) {
return mSpanIndexCache.keyAt(index);
}
return -1;
}
/**
* Returns the index of the group this position belongs.
* <p>
* For example, if grid has 3 columns and each item occupies 1 span, span group index
* for item 1 will be 0, item 5 will be 1.
*
* @param adapterPosition The position in adapter
* @param spanCount The total number of spans in the grid
* @return The index of the span group including the item at the given adapter position
*/
public int getSpanGroupIndex(int adapterPosition, int spanCount) {
int span = 0;
int group = 0;
int positionSpanSize = getSpanSize(adapterPosition);
for (int i = 0; i < adapterPosition; i++) {
int size = getSpanSize(i);
span += size;
if (span == spanCount) {
span = 0;
group++;
} else if (span > spanCount) {
// did not fit, moving to next row / column
span = size;
group++;
}
}
if (span + positionSpanSize > spanCount) {
group++;
}
return group;
}
}
@Override
public boolean supportsPredictiveItemAnimations() {
return mPendingSavedState == null;
}
/**
* Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
*/
public static final class DefaultSpanSizeLookup extends SpanSizeLookup {
@Override
public int getSpanSize(int position) {
return 1;
}
@Override
public int getSpanIndex(int position, int spanCount) {
return position % spanCount;
}
}
/**
* LayoutParams used by GridLayoutManager.
*/
public static class LayoutParams extends RecyclerView.LayoutParams {
/**
* Span Id for Views that are not laid out yet.
*/
public static final int INVALID_SPAN_ID = -1;
private int mSpanIndex = INVALID_SPAN_ID;
private int mSpanSize = 0;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(RecyclerView.LayoutParams source) {
super(source);
}
/**
* Returns the current span index of this View. If the View is not laid out yet, the return
* value is <code>undefined</code>.
* <p>
* Note that span index may change by whether the RecyclerView is RTL or not. For
* example, if the number of spans is 3 and layout is RTL, the rightmost item will have
* span index of 2. If the layout changes back to LTR, span index for this view will be 0.
* If the item was occupying 2 spans, span indices would be 1 and 0 respectively.
* <p>
* If the View occupies multiple spans, span with the minimum index is returned.
*
* @return The span index of the View.
*/
public int getSpanIndex() {
return mSpanIndex;
}
/**
* Returns the number of spans occupied by this View. If the View not laid out yet, the
* return value is <code>undefined</code>.
*
* @return The number of spans occupied by this View.
*/
public int getSpanSize() {
return mSpanSize;
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific languag`e governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.view.View;
/**
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space.
*/
class LayoutState {
final static String TAG = "LayoutState";
final static int LAYOUT_START = -1;
final static int LAYOUT_END = 1;
final static int INVALID_LAYOUT = Integer.MIN_VALUE;
final static int ITEM_DIRECTION_HEAD = -1;
final static int ITEM_DIRECTION_TAIL = 1;
final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
/**
* Number of pixels that we should fill, in the layout direction.
*/
int mAvailable;
/**
* Current position on the adapter to get the next item.
*/
int mCurrentPosition;
/**
* Defines the direction in which the data adapter is traversed.
* Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
*/
int mItemDirection;
/**
* Defines the direction in which the layout is filled.
* Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
*/
int mLayoutDirection;
/**
* Used if you want to pre-layout items that are not yet visible.
* The difference with {@link #mAvailable} is that, when recycling, distance rendered for
* {@link #mExtra} is not considered not to recycle visible children.
*/
int mExtra = 0;
/**
* @return true if there are more items in the data adapter
*/
boolean hasMore(RecyclerView.State state) {
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}
/**
* Gets the view for the next element that we should render.
* Also updates current item index to the next item, based on {@link #mItemDirection}
*
* @return The next element that we should render.
*/
View next(RecyclerView.Recycler recycler) {
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,338 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.Context;
import android.graphics.PointF;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
/**
* {@link RecyclerView.SmoothScroller} implementation which uses
* {@link LinearInterpolator} until the target position becames a child of
* the RecyclerView and then uses
* {@link DecelerateInterpolator} to slowly approach to target position.
*/
abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
private static final String TAG = "LinearSmoothScroller";
private static final boolean DEBUG = false;
private static final float MILLISECONDS_PER_INCH = 25f;
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
/**
* Align child view's left or top with parent view's left or top
*
* @see #calculateDtToFit(int, int, int, int, int)
* @see #calculateDxToMakeVisible(View, int)
* @see #calculateDyToMakeVisible(View, int)
*/
public static final int SNAP_TO_START = -1;
/**
* Align child view's right or bottom with parent view's right or bottom
*
* @see #calculateDtToFit(int, int, int, int, int)
* @see #calculateDxToMakeVisible(View, int)
* @see #calculateDyToMakeVisible(View, int)
*/
public static final int SNAP_TO_END = 1;
/**
* <p>Decides if the child should be snapped from start or end, depending on where it
* currently is in relation to its parent.</p>
* <p>For instance, if the view is virtually on the left of RecyclerView, using
* {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
*
* @see #calculateDtToFit(int, int, int, int, int)
* @see #calculateDxToMakeVisible(View, int)
* @see #calculateDyToMakeVisible(View, int)
*/
public static final int SNAP_TO_ANY = 0;
// Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
// view is not laid out until interim target position is reached, we can detect the case before
// scrolling slows down and reschedule another interim target scroll
private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
protected PointF mTargetVector;
private final float MILLISECONDS_PER_PX;
// Temporary variables to keep track of the interim scroll target. These values do not
// point to a real item position, rather point to an estimated location pixels.
protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
public LinearSmoothScroller(Context context) {
MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
}
/**
* {@inheritDoc}
*/
@Override
protected void onStart() {
}
/**
* {@inheritDoc}
*/
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
final int distance = (int) Math.sqrt(dx * dx + dy * dy);
final int time = calculateTimeForDeceleration(distance);
if (time > 0) {
action.update(-dx, -dy, time, mDecelerateInterpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
if (getChildCount() == 0) {
stop();
return;
}
if (DEBUG && mTargetVector != null
&& ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
throw new IllegalStateException("Scroll happened in the opposite direction"
+ " of the target. Some calculations are wrong");
}
mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
updateActionForInterimTarget(action);
} // everything is valid, keep going
}
/**
* {@inheritDoc}
*/
@Override
protected void onStop() {
mInterimTargetDx = mInterimTargetDy = 0;
mTargetVector = null;
}
/**
* Calculates the scroll speed.
*
* @param displayMetrics DisplayMetrics to be used for real dimension calculations
* @return The time (in ms) it should take for each pixel. For instance, if returned value is
* 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
*/
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
/**
* <p>Calculates the time for deceleration so that transition from LinearInterpolator to
* DecelerateInterpolator looks smooth.</p>
*
* @param dx Distance to scroll
* @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
* from LinearInterpolation
*/
protected int calculateTimeForDeceleration(int dx) {
// we want to cover same area with the linear interpolator for the first 10% of the
// interpolation. After that, deceleration will take control.
// area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
// which gives 0.100028 when x = .3356
// this is why we divide linear scrolling time with .3356
return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
}
/**
* Calculates the time it should take to scroll the given distance (in pixels)
*
* @param dx Distance in pixels that we want to scroll
* @return Time in milliseconds
* @see #calculateSpeedPerPixel(DisplayMetrics)
*/
protected int calculateTimeForScrolling(int dx) {
// In a case where dx is very small, rounding may return 0 although dx > 0.
// To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
// time.
return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
}
/**
* When scrolling towards a child view, this method defines whether we should align the left
* or the right edge of the child with the parent RecyclerView.
*
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
* @see #SNAP_TO_START
* @see #SNAP_TO_END
* @see #SNAP_TO_ANY
*/
protected int getHorizontalSnapPreference() {
return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
}
/**
* When scrolling towards a child view, this method defines whether we should align the top
* or the bottom edge of the child with the parent RecyclerView.
*
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
* @see #SNAP_TO_START
* @see #SNAP_TO_END
* @see #SNAP_TO_ANY
*/
protected int getVerticalSnapPreference() {
return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
}
/**
* When the target scroll position is not a child of the RecyclerView, this method calculates
* a direction vector towards that child and triggers a smooth scroll.
*
* @see #computeScrollVectorForPosition(int)
*/
protected void updateActionForInterimTarget(Action action) {
// find an interim target position
PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
Log.e(TAG, "To support smooth scrolling, you should override \n"
+ "LayoutManager#computeScrollVectorForPosition.\n"
+ "Falling back to instant scroll");
final int target = getTargetPosition();
stop();
instantScrollToPosition(target);
return;
}
normalize(scrollVector);
mTargetVector = scrollVector;
mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
// To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
// interim target. Since we track the distance travelled in onSeekTargetStep callback, it
// won't actually scroll more than what we need.
action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
, (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
, (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
}
private int clampApplyScroll(int tmpDt, int dt) {
final int before = tmpDt;
tmpDt -= dt;
if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
return 0;
}
return tmpDt;
}
/**
* Helper method for {@link #calculateDxToMakeVisible(View, int)} and
* {@link #calculateDyToMakeVisible(View, int)}
*/
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
snapPreference) {
switch (snapPreference) {
case SNAP_TO_START:
return boxStart - viewStart;
case SNAP_TO_END:
return boxEnd - viewEnd;
case SNAP_TO_ANY:
final int dtStart = boxStart - viewStart;
if (dtStart > 0) {
return dtStart;
}
final int dtEnd = boxEnd - viewEnd;
if (dtEnd < 0) {
return dtEnd;
}
break;
default:
throw new IllegalArgumentException("snap preference should be one of the"
+ " constants defined in SmoothScroller, starting with SNAP_");
}
return 0;
}
/**
* Calculates the vertical scroll amount necessary to make the given view fully visible
* inside the RecyclerView.
*
* @param view The view which we want to make fully visible
* @param snapPreference The edge which the view should snap to when entering the visible
* area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
* {@link #SNAP_TO_END}.
* @return The vertical scroll amount necessary to make the view visible with the given
* snap preference.
*/
public int calculateDyToMakeVisible(View view, int snapPreference) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (!layoutManager.canScrollVertically()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
final int start = layoutManager.getPaddingTop();
final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
return calculateDtToFit(top, bottom, start, end, snapPreference);
}
/**
* Calculates the horizontal scroll amount necessary to make the given view fully visible
* inside the RecyclerView.
*
* @param view The view which we want to make fully visible
* @param snapPreference The edge which the view should snap to when entering the visible
* area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
* {@link #SNAP_TO_END}
* @return The vertical scroll amount necessary to make the view visible with the given
* snap preference.
*/
public int calculateDxToMakeVisible(View view, int snapPreference) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (!layoutManager.canScrollHorizontally()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
final int start = layoutManager.getPaddingLeft();
final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
return calculateDtToFit(left, right, start, end, snapPreference);
}
abstract public PointF computeScrollVectorForPosition(int targetPosition);
}

View File

@ -0,0 +1,238 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.support.v7.widget.AdapterHelper.UpdateOp;
import java.util.List;
import static android.support.v7.widget.AdapterHelper.UpdateOp.ADD;
import static android.support.v7.widget.AdapterHelper.UpdateOp.MOVE;
import static android.support.v7.widget.AdapterHelper.UpdateOp.REMOVE;
import static android.support.v7.widget.AdapterHelper.UpdateOp.UPDATE;
class OpReorderer {
final Callback mCallback;
public OpReorderer(Callback callback) {
mCallback = callback;
}
void reorderOps(List<UpdateOp> ops) {
// since move operations breaks continuity, their effects on ADD/RM are hard to handle.
// we push them to the end of the list so that they can be handled easily.
int badMove;
while ((badMove = getLastMoveOutOfOrder(ops)) != -1) {
swapMoveOp(ops, badMove, badMove + 1);
}
}
private void swapMoveOp(List<UpdateOp> list, int badMove, int next) {
final UpdateOp moveOp = list.get(badMove);
final UpdateOp nextOp = list.get(next);
switch (nextOp.cmd) {
case REMOVE:
swapMoveRemove(list, badMove, moveOp, next, nextOp);
break;
case ADD:
swapMoveAdd(list, badMove, moveOp, next, nextOp);
break;
case UPDATE:
swapMoveUpdate(list, badMove, moveOp, next, nextOp);
break;
}
}
void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp,
int removePos, UpdateOp removeOp) {
UpdateOp extraRm = null;
// check if move is nulled out by remove
boolean revertedMove = false;
final boolean moveIsBackwards;
if (moveOp.positionStart < moveOp.itemCount) {
moveIsBackwards = false;
if (removeOp.positionStart == moveOp.positionStart
&& removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) {
revertedMove = true;
}
} else {
moveIsBackwards = true;
if (removeOp.positionStart == moveOp.itemCount + 1 &&
removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) {
revertedMove = true;
}
}
// going in reverse, first revert the effect of add
if (moveOp.itemCount < removeOp.positionStart) {
removeOp.positionStart--;
} else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) {
// move is removed.
removeOp.itemCount --;
moveOp.cmd = REMOVE;
moveOp.itemCount = 1;
if (removeOp.itemCount == 0) {
list.remove(removePos);
mCallback.recycleUpdateOp(removeOp);
}
// no need to swap, it is already a remove
return;
}
// now affect of add is consumed. now apply effect of first remove
if (moveOp.positionStart <= removeOp.positionStart) {
removeOp.positionStart++;
} else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) {
final int remaining = removeOp.positionStart + removeOp.itemCount
- moveOp.positionStart;
extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining);
removeOp.itemCount = moveOp.positionStart - removeOp.positionStart;
}
// if effects of move is reverted by remove, we are done.
if (revertedMove) {
list.set(movePos, removeOp);
list.remove(removePos);
mCallback.recycleUpdateOp(moveOp);
return;
}
// now find out the new locations for move actions
if (moveIsBackwards) {
if (extraRm != null) {
if (moveOp.positionStart > extraRm.positionStart) {
moveOp.positionStart -= extraRm.itemCount;
}
if (moveOp.itemCount > extraRm.positionStart) {
moveOp.itemCount -= extraRm.itemCount;
}
}
if (moveOp.positionStart > removeOp.positionStart) {
moveOp.positionStart -= removeOp.itemCount;
}
if (moveOp.itemCount > removeOp.positionStart) {
moveOp.itemCount -= removeOp.itemCount;
}
} else {
if (extraRm != null) {
if (moveOp.positionStart >= extraRm.positionStart) {
moveOp.positionStart -= extraRm.itemCount;
}
if (moveOp.itemCount >= extraRm.positionStart) {
moveOp.itemCount -= extraRm.itemCount;
}
}
if (moveOp.positionStart >= removeOp.positionStart) {
moveOp.positionStart -= removeOp.itemCount;
}
if (moveOp.itemCount >= removeOp.positionStart) {
moveOp.itemCount -= removeOp.itemCount;
}
}
list.set(movePos, removeOp);
if (moveOp.positionStart != moveOp.itemCount) {
list.set(removePos, moveOp);
} else {
list.remove(removePos);
}
if (extraRm != null) {
list.add(movePos, extraRm);
}
}
private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add,
UpdateOp addOp) {
int offset = 0;
// going in reverse, first revert the effect of add
if (moveOp.itemCount < addOp.positionStart) {
offset--;
}
if (moveOp.positionStart < addOp.positionStart) {
offset++;
}
if (addOp.positionStart <= moveOp.positionStart) {
moveOp.positionStart += addOp.itemCount;
}
if (addOp.positionStart <= moveOp.itemCount) {
moveOp.itemCount += addOp.itemCount;
}
addOp.positionStart += offset;
list.set(move, addOp);
list.set(add, moveOp);
}
void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update,
UpdateOp updateOp) {
UpdateOp extraUp1 = null;
UpdateOp extraUp2 = null;
// going in reverse, first revert the effect of add
if (moveOp.itemCount < updateOp.positionStart) {
updateOp.positionStart--;
} else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) {
// moved item is updated. add an update for it
updateOp.itemCount--;
extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1);
}
// now affect of add is consumed. now apply effect of first remove
if (moveOp.positionStart <= updateOp.positionStart) {
updateOp.positionStart++;
} else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) {
final int remaining = updateOp.positionStart + updateOp.itemCount
- moveOp.positionStart;
extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining);
updateOp.itemCount -= remaining;
}
list.set(update, moveOp);
if (updateOp.itemCount > 0) {
list.set(move, updateOp);
} else {
list.remove(move);
mCallback.recycleUpdateOp(updateOp);
}
if (extraUp1 != null) {
list.add(move, extraUp1);
}
if (extraUp2 != null) {
list.add(move, extraUp2);
}
}
private int getLastMoveOutOfOrder(List<UpdateOp> list) {
boolean foundNonMove = false;
for (int i = list.size() - 1; i >= 0; i--) {
final UpdateOp op1 = list.get(i);
if (op1.cmd == MOVE) {
if (foundNonMove) {
return i;
}
} else {
foundNonMove = true;
}
}
return -1;
}
static interface Callback {
UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount);
void recycleUpdateOp(UpdateOp op);
}
}

View File

@ -0,0 +1,338 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.view.View;
import android.widget.LinearLayout;
/**
* Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
* <p>
* It is developed to easily support vertical and horizontal orientations in a LayoutManager but
* can also be used to abstract calls around view bounds and child measurements with margins and
* decorations.
*
* @see #createHorizontalHelper(RecyclerView.LayoutManager)
* @see #createVerticalHelper(RecyclerView.LayoutManager)
*/
public abstract class OrientationHelper {
private static final int INVALID_SIZE = Integer.MIN_VALUE;
protected final RecyclerView.LayoutManager mLayoutManager;
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private int mLastTotalSpace = INVALID_SIZE;
private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
mLayoutManager = layoutManager;
}
/**
* Call this method after onLayout method is complete if state is NOT pre-layout.
* This method records information like layout bounds that might be useful in the next layout
* calculations.
*/
public void onLayoutComplete() {
mLastTotalSpace = getTotalSpace();
}
/**
* Returns the layout space change between the previous layout pass and current layout pass.
* <p>
* Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
* {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
* RecyclerView.State)} method.
*
* @return The difference between the current total space and previous layout's total space.
* @see #onLayoutComplete()
*/
public int getTotalSpaceChange() {
return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
}
/**
* Returns the start of the view including its decoration and margin.
* <p>
* For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
* decoration and 3px left margin, returned value will be 15px.
*
* @param view The view element to check
* @return The first pixel of the element
* @see #getDecoratedEnd(View)
*/
public abstract int getDecoratedStart(View view);
/**
* Returns the end of the view including its decoration and margin.
* <p>
* For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
* decoration and 3px right margin, returned value will be 205.
*
* @param view The view element to check
* @return The last pixel of the element
* @see #getDecoratedStart(View)
*/
public abstract int getDecoratedEnd(View view);
/**
* Returns the space occupied by this View in the current orientation including decorations and
* margins.
*
* @param view The view element to check
* @return Total space occupied by this view
* @see #getDecoratedMeasurementInOther(View)
*/
public abstract int getDecoratedMeasurement(View view);
/**
* Returns the space occupied by this View in the perpendicular orientation including
* decorations and margins.
*
* @param view The view element to check
* @return Total space occupied by this view in the perpendicular orientation to current one
* @see #getDecoratedMeasurement(View)
*/
public abstract int getDecoratedMeasurementInOther(View view);
/**
* Returns the start position of the layout after the start padding is added.
*
* @return The very first pixel we can draw.
*/
public abstract int getStartAfterPadding();
/**
* Returns the end position of the layout after the end padding is removed.
*
* @return The end boundary for this layout.
*/
public abstract int getEndAfterPadding();
/**
* Returns the end position of the layout without taking padding into account.
*
* @return The end boundary for this layout without considering padding.
*/
public abstract int getEnd();
/**
* Offsets all children's positions by the given amount.
*
* @param amount Value to add to each child's layout parameters
*/
public abstract void offsetChildren(int amount);
/**
* Returns the total space to layout. This number is the difference between
* {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
*
* @return Total space to layout children
*/
public abstract int getTotalSpace();
/**
* Offsets the child in this orientation.
*
* @param view View to offset
* @param offset offset amount
*/
public abstract void offsetChild(View view, int offset);
/**
* Returns the padding at the end of the layout. For horizontal helper, this is the right
* padding and for vertical helper, this is the bottom padding. This method does not check
* whether the layout is RTL or not.
*
* @return The padding at the end of the layout.
*/
public abstract int getEndPadding();
/**
* Creates an OrientationHelper for the given LayoutManager and orientation.
*
* @param layoutManager LayoutManager to attach to
* @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
* @return A new OrientationHelper
*/
public static OrientationHelper createOrientationHelper(
RecyclerView.LayoutManager layoutManager, int orientation) {
switch (orientation) {
case HORIZONTAL:
return createHorizontalHelper(layoutManager);
case VERTICAL:
return createVerticalHelper(layoutManager);
}
throw new IllegalArgumentException("invalid orientation");
}
/**
* Creates a horizontal OrientationHelper for the given LayoutManager.
*
* @param layoutManager The LayoutManager to attach to.
* @return A new OrientationHelper
*/
public static OrientationHelper createHorizontalHelper(
RecyclerView.LayoutManager layoutManager) {
return new OrientationHelper(layoutManager) {
@Override
public int getEndAfterPadding() {
return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
}
@Override
public int getEnd() {
return mLayoutManager.getWidth();
}
@Override
public void offsetChildren(int amount) {
mLayoutManager.offsetChildrenHorizontal(amount);
}
@Override
public int getStartAfterPadding() {
return mLayoutManager.getPaddingLeft();
}
@Override
public int getDecoratedMeasurement(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ params.rightMargin;
}
@Override
public int getDecoratedMeasurementInOther(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ params.bottomMargin;
}
@Override
public int getDecoratedEnd(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
}
@Override
public int getDecoratedStart(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
}
@Override
public int getTotalSpace() {
return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
- mLayoutManager.getPaddingRight();
}
@Override
public void offsetChild(View view, int offset) {
view.offsetLeftAndRight(offset);
}
@Override
public int getEndPadding() {
return mLayoutManager.getPaddingRight();
}
};
}
/**
* Creates a vertical OrientationHelper for the given LayoutManager.
*
* @param layoutManager The LayoutManager to attach to.
* @return A new OrientationHelper
*/
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
return new OrientationHelper(layoutManager) {
@Override
public int getEndAfterPadding() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
}
@Override
public int getEnd() {
return mLayoutManager.getHeight();
}
@Override
public void offsetChildren(int amount) {
mLayoutManager.offsetChildrenVertical(amount);
}
@Override
public int getStartAfterPadding() {
return mLayoutManager.getPaddingTop();
}
@Override
public int getDecoratedMeasurement(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+ params.bottomMargin;
}
@Override
public int getDecoratedMeasurementInOther(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+ params.rightMargin;
}
@Override
public int getDecoratedEnd(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
}
@Override
public int getDecoratedStart(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedTop(view) - params.topMargin;
}
@Override
public int getTotalSpace() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
- mLayoutManager.getPaddingBottom();
}
@Override
public void offsetChild(View view, int offset) {
view.offsetTopAndBottom(offset);
}
@Override
public int getEndPadding() {
return mLayoutManager.getPaddingBottom();
}
};
}
}

View File

@ -0,0 +1,460 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import java.util.ArrayList;
/**
* Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions.
*/
class PositionMap<E> implements Cloneable {
private static final Object DELETED = new Object();
private boolean mGarbage = false;
private int[] mKeys;
private Object[] mValues;
private int mSize;
/**
* Creates a new SparseArray containing no mappings.
*/
public PositionMap() {
this(10);
}
/**
* Creates a new PositionMap containing no mappings that will not
* require any additional memory allocation to store the specified
* number of mappings. If you supply an initial capacity of 0, the
* sparse array will be initialized with a light-weight representation
* not requiring any additional array allocations.
*/
public PositionMap(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = ContainerHelpers.EMPTY_INTS;
mValues = ContainerHelpers.EMPTY_OBJECTS;
} else {
initialCapacity = idealIntArraySize(initialCapacity);
mKeys = new int[initialCapacity];
mValues = new Object[initialCapacity];
}
mSize = 0;
}
@Override
@SuppressWarnings("unchecked")
public PositionMap<E> clone() {
PositionMap<E> clone = null;
try {
clone = (PositionMap<E>) super.clone();
clone.mKeys = mKeys.clone();
clone.mValues = mValues.clone();
} catch (CloneNotSupportedException cnse) {
/* ignore */
}
return clone;
}
/**
* Gets the Object mapped from the specified key, or <code>null</code>
* if no such mapping has been made.
*/
public E get(int key) {
return get(key, null);
}
/**
* Gets the Object mapped from the specified key, or the specified Object
* if no such mapping has been made.
*/
@SuppressWarnings("unchecked")
public E get(int key, E valueIfKeyNotFound) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
/**
* Removes the mapping from the specified key, if there was any.
*/
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
/**
* Alias for {@link #delete(int)}.
*/
public void remove(int key) {
delete(key);
}
/**
* Removes the mapping at the specified index.
*/
public void removeAt(int index) {
if (mValues[index] != DELETED) {
mValues[index] = DELETED;
mGarbage = true;
}
}
/**
* Remove a range of mappings as a batch.
*
* @param index Index to begin at
* @param size Number of mappings to remove
*/
public void removeAtRange(int index, int size) {
final int end = Math.min(mSize, index + size);
for (int i = index; i < end; i++) {
removeAt(i);
}
}
public void insertKeyRange(int keyStart, int count) {
}
public void removeKeyRange(ArrayList<E> removedItems, int keyStart, int count) {
}
private void gc() {
// Log.e("SparseArray", "gc start with " + mSize);
int n = mSize;
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
Object val = values[i];
if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;
// Log.e("SparseArray", "gc end with " + mSize);
}
/**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
*/
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
mValues[i] = value;
} else {
i = ~i;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
if (mSize >= mKeys.length) {
int n = idealIntArraySize(mSize + 1);
int[] nkeys = new int[n];
Object[] nvalues = new Object[n];
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
mKeys = nkeys;
mValues = nvalues;
}
if (mSize - i != 0) {
// Log.e("SparseArray", "move " + (mSize - i));
System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
}
mKeys[i] = key;
mValues[i] = value;
mSize++;
}
}
/**
* Returns the number of key-value mappings that this SparseArray
* currently stores.
*/
public int size() {
if (mGarbage) {
gc();
}
return mSize;
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public int keyAt(int index) {
if (mGarbage) {
gc();
}
return mKeys[index];
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the value from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
@SuppressWarnings("unchecked")
public E valueAt(int index) {
if (mGarbage) {
gc();
}
return (E) mValues[index];
}
/**
* Given an index in the range <code>0...size()-1</code>, sets a new
* value for the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public void setValueAt(int index, E value) {
if (mGarbage) {
gc();
}
mValues[index] = value;
}
/**
* Returns the index for which {@link #keyAt} would return the
* specified key, or a negative number if the specified
* key is not mapped.
*/
public int indexOfKey(int key) {
if (mGarbage) {
gc();
}
return ContainerHelpers.binarySearch(mKeys, mSize, key);
}
/**
* Returns an index for which {@link #valueAt} would return the
* specified key, or a negative number if no keys map to the
* specified value.
* <p>Beware that this is a linear search, unlike lookups by key,
* and that multiple keys can map to the same value and this will
* find only one of them.
* <p>Note also that unlike most collections' {@code indexOf} methods,
* this method compares values using {@code ==} rather than {@code equals}.
*/
public int indexOfValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++)
if (mValues[i] == value)
return i;
return -1;
}
/**
* Removes all key-value mappings from this SparseArray.
*/
public void clear() {
int n = mSize;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
values[i] = null;
}
mSize = 0;
mGarbage = false;
}
/**
* Puts a key/value pair into the array, optimizing for the case where
* the key is greater than all existing keys in the array.
*/
public void append(int key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
put(key, value);
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
}
int pos = mSize;
if (pos >= mKeys.length) {
int n = idealIntArraySize(pos + 1);
int[] nkeys = new int[n];
Object[] nvalues = new Object[n];
// Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
mKeys = nkeys;
mValues = nvalues;
}
mKeys[pos] = key;
mValues[pos] = value;
mSize = pos + 1;
}
/**
* {@inheritDoc}
*
* <p>This implementation composes a string by iterating over its mappings. If
* this map contains itself as a value, the string "(this Map)"
* will appear in its place.
*/
@Override
public String toString() {
if (size() <= 0) {
return "{}";
}
StringBuilder buffer = new StringBuilder(mSize * 28);
buffer.append('{');
for (int i=0; i<mSize; i++) {
if (i > 0) {
buffer.append(", ");
}
int key = keyAt(i);
buffer.append(key);
buffer.append('=');
Object value = valueAt(i);
if (value != this) {
buffer.append(value);
} else {
buffer.append("(this Map)");
}
}
buffer.append('}');
return buffer.toString();
}
static int idealByteArraySize(int need) {
for (int i = 4; i < 32; i++)
if (need <= (1 << i) - 12)
return (1 << i) - 12;
return need;
}
static int idealBooleanArraySize(int need) {
return idealByteArraySize(need);
}
static int idealShortArraySize(int need) {
return idealByteArraySize(need * 2) / 2;
}
static int idealCharArraySize(int need) {
return idealByteArraySize(need * 2) / 2;
}
static int idealIntArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}
static int idealFloatArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}
static int idealObjectArraySize(int need) {
return idealByteArraySize(need * 4) / 4;
}
static int idealLongArraySize(int need) {
return idealByteArraySize(need * 8) / 8;
}
static class ContainerHelpers {
static final boolean[] EMPTY_BOOLEANS = new boolean[0];
static final int[] EMPTY_INTS = new int[0];
static final long[] EMPTY_LONGS = new long[0];
static final Object[] EMPTY_OBJECTS = new Object[0];
// This is Arrays.binarySearch(), but doesn't do any argument validation.
static int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
final int mid = (lo + hi) >>> 1;
final int midVal = array[mid];
if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid; // value found
}
}
return ~lo; // value not present
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.os.Bundle;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
/**
* The AccessibilityDelegate used by RecyclerView.
* <p>
* This class handles basic accessibility actions and delegates them to LayoutManager.
*/
public class RecyclerViewAccessibilityDelegate extends AccessibilityDelegateCompat {
final RecyclerView mRecyclerView;
public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (super.performAccessibilityAction(host, action, args)) {
return true;
}
if (mRecyclerView.getLayoutManager() != null) {
return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args);
}
return false;
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(RecyclerView.class.getName());
if (mRecyclerView.getLayoutManager() != null) {
mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info);
}
}
@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
event.setClassName(RecyclerView.class.getName());
if (host instanceof RecyclerView) {
RecyclerView rv = (RecyclerView) host;
if (rv.getLayoutManager() != null) {
rv.getLayoutManager().onInitializeAccessibilityEvent(event);
}
}
}
AccessibilityDelegateCompat getItemDelegate() {
return mItemDelegate;
}
final AccessibilityDelegateCompat mItemDelegate = new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
if (mRecyclerView.getLayoutManager() != null) {
mRecyclerView.getLayoutManager().
onInitializeAccessibilityNodeInfoForItem(host, info);
}
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (super.performAccessibilityAction(host, action, args)) {
return true;
}
if (mRecyclerView.getLayoutManager() != null) {
return mRecyclerView.getLayoutManager().
performAccessibilityActionForItem(host, action, args);
}
return false;
}
};
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.view.View;
/**
* A helper class to do scroll offset calculations.
*/
class ScrollbarHelper {
/**
* @param startChild View closest to start of the list. (top or left)
* @param endChild View closest to end of the list (bottom or right)
*/
static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
View startChild, View endChild, RecyclerView.LayoutManager lm,
boolean smoothScrollbarEnabled, boolean reverseLayout) {
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
endChild == null) {
return 0;
}
final int minPosition = Math.min(lm.getPosition(startChild), lm.getPosition(endChild));
final int maxPosition = Math.max(lm.getPosition(startChild), lm.getPosition(endChild));
final int itemsBefore = reverseLayout
? Math.max(0, state.getItemCount() - maxPosition - 1)
: Math.max(0, minPosition - 1);
if (!smoothScrollbarEnabled) {
return itemsBefore;
}
final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
orientation.getDecoratedStart(startChild));
final int itemRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
final float avgSizePerRow = (float) laidOutArea / itemRange;
return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
- orientation.getDecoratedStart(startChild)));
}
/**
* @param startChild View closest to start of the list. (top or left)
* @param endChild View closest to end of the list (bottom or right)
*/
static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation,
View startChild, View endChild, RecyclerView.LayoutManager lm,
boolean smoothScrollbarEnabled) {
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
endChild == null) {
return 0;
}
if (!smoothScrollbarEnabled) {
return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
}
final int extend = orientation.getDecoratedEnd(endChild)
- orientation.getDecoratedStart(startChild);
return Math.min(orientation.getTotalSpace(), extend);
}
/**
* @param startChild View closest to start of the list. (top or left)
* @param endChild View closest to end of the list (bottom or right)
*/
static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation,
View startChild, View endChild, RecyclerView.LayoutManager lm,
boolean smoothScrollbarEnabled) {
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
endChild == null) {
return 0;
}
if (!smoothScrollbarEnabled) {
return state.getItemCount();
}
// smooth scrollbar enabled. try to estimate better.
final int laidOutArea = orientation.getDecoratedEnd(endChild) -
orientation.getDecoratedStart(startChild);
final int laidOutRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild))
+ 1;
// estimate a size for full list.
return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,159 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.swiperefreshlayout.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
/**
* Private class created to work around issues with AnimationListeners being
* called before the animation is actually complete and support shadows on older
* platforms.
*/
class CircleImageView extends ImageView {
private static final int KEY_SHADOW_COLOR = 0x1E000000;
private static final int FILL_SHADOW_COLOR = 0x3D000000;
// PX
private static final float X_OFFSET = 0f;
private static final float Y_OFFSET = 1.75f;
private static final float SHADOW_RADIUS = 3.5f;
private static final int SHADOW_ELEVATION = 4;
private Animation.AnimationListener mListener;
int mShadowRadius;
CircleImageView(Context context, int color) {
super(context);
final float density = getContext().getResources().getDisplayMetrics().density;
final int shadowYOffset = (int) (density * Y_OFFSET);
final int shadowXOffset = (int) (density * X_OFFSET);
mShadowRadius = (int) (density * SHADOW_RADIUS);
ShapeDrawable circle;
if (elevationSupported()) {
circle = new ShapeDrawable(new OvalShape());
ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
} else {
OvalShape oval = new OvalShadow(mShadowRadius);
circle = new ShapeDrawable(oval);
setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint());
circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
}
circle.getPaint().setColor(color);
ViewCompat.setBackground(this, circle);
}
private boolean elevationSupported() {
return android.os.Build.VERSION.SDK_INT >= 21;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!elevationSupported()) {
setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight()
+ mShadowRadius * 2);
}
}
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
@Override
public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
/**
* Update the background color of the circle image view.
*
* @param colorRes Id of a color resource.
*/
public void setBackgroundColorRes(int colorRes) {
setBackgroundColor(ContextCompat.getColor(getContext(), colorRes));
}
@Override
public void setBackgroundColor(int color) {
if (getBackground() instanceof ShapeDrawable) {
((ShapeDrawable) getBackground()).getPaint().setColor(color);
}
}
private class OvalShadow extends OvalShape {
private RadialGradient mRadialGradient;
private Paint mShadowPaint;
OvalShadow(int shadowRadius) {
super();
mShadowPaint = new Paint();
mShadowRadius = shadowRadius;
updateRadialGradient((int) rect().width());
}
@Override
protected void onResize(float width, float height) {
super.onResize(width, height);
updateRadialGradient((int) width);
}
@Override
public void draw(Canvas canvas, Paint paint) {
final int viewWidth = CircleImageView.this.getWidth();
final int viewHeight = CircleImageView.this.getHeight();
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);
canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);
}
private void updateRadialGradient(int diameter) {
mRadialGradient = new RadialGradient(diameter / 2, diameter / 2,
mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT },
null, Shader.TileMode.CLAMP);
mShadowPaint.setShader(mRadialGradient);
}
}
}

View File

@ -1,58 +0,0 @@
package androidx.swiperefreshlayout.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
public class ViewPagerSwipeRefreshLayout extends SwipeRefreshLayout {
private float startY;
private float startX;
// 记录viewPager是否拖拽的标记
private boolean mIsVpDragger;
private final int mTouchSlop;
public ViewPagerSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 记录手指按下的位置
startY = ev.getY();
startX = ev.getX();
// 初始化标记
mIsVpDragger = false;
break;
case MotionEvent.ACTION_MOVE:
// 如果viewpager正在拖拽中那么不拦截它的事件直接return false
if(mIsVpDragger) {
return false;
}
// 获取当前手指位置
float endY = ev.getY();
float endX = ev.getX();
float distanceX = Math.abs(endX - startX);
float distanceY = Math.abs(endY - startY);
// 如果X轴位移大于Y轴位移那么将事件交给viewPager处理。
if(distanceX > mTouchSlop && distanceX > distanceY) {
mIsVpDragger = true;
return false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 初始化标记
mIsVpDragger = false;
break;
}
// 如果是Y轴位移大于X轴事件交给swipeRefreshLayout处理。
return super.onInterceptTouchEvent(ev);
}
}

View File

@ -0,0 +1,55 @@
package com.gc.materialdesign.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
public class CustomView extends RelativeLayout{
final static String MATERIALDESIGNXML = "http://schemas.android.com/apk/res-auto";
final static String ANDROIDXML = "http://schemas.android.com/apk/res/android";
final int disabledBackgroundColor = Color.parseColor("#E2E2E2");
int beforeBackground;
// Indicate if user touched this view the last time
public boolean isLastTouch = false;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if(enabled)
setBackgroundColor(beforeBackground);
else
setBackgroundColor(disabledBackgroundColor);
invalidate();
}
boolean animation = false;
@Override
protected void onAnimationStart() {
super.onAnimationStart();
animation = true;
}
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();
animation = false;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(animation)
invalidate();
}
}

View File

@ -0,0 +1,183 @@
package com.gc.materialdesign.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
public class ProgressBarCircularIndeterminate extends CustomView {
final static String ANDROIDXML = "http://schemas.android.com/apk/res/android";
int backgroundColor = Color.parseColor("#1E88E5");
public ProgressBarCircularIndeterminate(Context context, AttributeSet attrs) {
super(context, attrs);
setAttributes(attrs);
}
// Set atributtes of XML to View
protected void setAttributes(AttributeSet attrs) {
setMinimumHeight(Utils.dpToPx(32, getResources()));
setMinimumWidth(Utils.dpToPx(32, getResources()));
// Set background Color
// Color by resource
int bacgroundColor = attrs.getAttributeResourceValue(ANDROIDXML,
"background", -1);
if (bacgroundColor != -1) {
setBackgroundColor(getResources().getColor(bacgroundColor));
} else {
// Color by hexadecimal
int background = attrs.getAttributeIntValue(ANDROIDXML,
"background", -1);
if (background != -1)
setBackgroundColor(background);
else
setBackgroundColor(Color.parseColor("#1E88E5"));
}
setMinimumHeight(Utils.dpToPx(3, getResources()));
}
/**
* Make a dark color to ripple effect
*
* @return
*/
protected int makePressColor() {
int r = (this.backgroundColor >> 16) & 0xFF;
int g = (this.backgroundColor >> 8) & 0xFF;
int b = (this.backgroundColor >> 0) & 0xFF;
// r = (r+90 > 245) ? 245 : r+90;
// g = (g+90 > 245) ? 245 : g+90;
// b = (b+90 > 245) ? 245 : b+90;
return Color.argb(128, r, g, b);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (firstAnimationOver == false)
drawFirstAnimation(canvas);
if (cont > 0)
drawSecondAnimation(canvas);
invalidate();
}
float radius1 = 0;
float radius2 = 0;
int cont = 0;
boolean firstAnimationOver = false;
/**
* Draw first animation of view
*
* @param canvas
*/
private void drawFirstAnimation(Canvas canvas) {
if (radius1 < getWidth() / 2) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(makePressColor());
radius1 = (radius1 >= getWidth() / 2) ? (float) getWidth() / 2
: radius1 + 1;
canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius1, paint);
} else {
Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(),
canvas.getHeight(), Bitmap.Config.ARGB_8888);
Canvas temp = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(makePressColor());
temp.drawCircle(getWidth() / 2, getHeight() / 2, getHeight() / 2,
paint);
Paint transparentPaint = new Paint();
transparentPaint.setAntiAlias(true);
transparentPaint.setColor(getResources().getColor(
android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
if (cont >= 50) {
radius2 = (radius2 >= getWidth() / 2) ? (float) getWidth() / 2
: radius2 + 1;
} else {
radius2 = (radius2 >= getWidth() / 2
- Utils.dpToPx(4, getResources())) ? (float) getWidth()
/ 2 - Utils.dpToPx(4, getResources()) : radius2 + 1;
}
temp.drawCircle(getWidth() / 2, getHeight() / 2, radius2,
transparentPaint);
canvas.drawBitmap(bitmap, 0, 0, new Paint());
if (radius2 >= getWidth() / 2 - Utils.dpToPx(4, getResources()))
cont++;
if (radius2 >= getWidth() / 2)
firstAnimationOver = true;
}
}
int arcD = 1;
int arcO = 0;
float rotateAngle = 0;
int limite = 0;
/**
* Draw second animation of view
*
* @param canvas
*/
private void drawSecondAnimation(Canvas canvas) {
if (arcO == limite)
arcD += 6;
if (arcD >= 290 || arcO > limite) {
arcO += 6;
arcD -= 6;
}
if (arcO > limite + 290) {
limite = arcO;
arcO = limite;
arcD = 1;
}
rotateAngle += 4;
canvas.rotate(rotateAngle, getWidth() / 2, getHeight() / 2);
Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(),
canvas.getHeight(), Bitmap.Config.ARGB_8888);
Canvas temp = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(backgroundColor);
// temp.drawARGB(0, 0, 0, 255);
temp.drawArc(new RectF(0, 0, getWidth(), getHeight()), arcO, arcD,
true, paint);
Paint transparentPaint = new Paint();
transparentPaint.setAntiAlias(true);
transparentPaint.setColor(getResources().getColor(
android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
temp.drawCircle(getWidth() / 2, getHeight() / 2, (getWidth() / 2)
- Utils.dpToPx(4, getResources()), transparentPaint);
canvas.drawBitmap(bitmap, 0, 0, new Paint());
}
// Set color of background
public void setBackgroundColor(int color) {
super.setBackgroundColor(getResources().getColor(
android.R.color.transparent));
if (isEnabled())
beforeBackground = backgroundColor;
this.backgroundColor = color;
}
}

View File

@ -0,0 +1,34 @@
package com.gc.materialdesign.views;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.View;
public class Utils {
/**
* Convert Dp to Pixel
*/
public static int dpToPx(float dp, Resources resources){
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
return (int) px;
}
public static int getRelativeTop(View myView) {
// if (myView.getParent() == myView.getRootView())
if(myView.getId() == android.R.id.content)
return myView.getTop();
else
return myView.getTop() + getRelativeTop((View) myView.getParent());
}
public static int getRelativeLeft(View myView) {
// if (myView.getParent() == myView.getRootView())
if(myView.getId() == android.R.id.content)
return myView.getLeft();
else
return myView.getLeft() + getRelativeLeft((View) myView.getParent());
}
}

View File

@ -0,0 +1,238 @@
package com.gh.base;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Application;
import android.content.Context;
import android.os.Process;
import android.support.v4.util.ArrayMap;
import android.util.Log;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.gh.common.util.DataUtils;
import com.gh.common.util.HttpsUtils;
import com.gh.common.util.TokenUtils;
import com.gh.common.util.Utils;
import com.umeng.message.IUmengRegisterCallback;
import com.umeng.message.PushAgent;
import com.umeng.message.UTrack;
import com.xiaomi.channel.commonutils.logger.LoggerInterface;
import com.xiaomi.mipush.sdk.Logger;
import com.xiaomi.mipush.sdk.MiPushClient;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class AppController extends Application {
//public class AppController extends TinkerApplication {
public static final String TAG = AppController.class.getSimpleName();
// xiaomi push appid
public static final String APP_ID = "2882303761517352993";
// xiaomi push appkey
public static final String APP_KEY = "5451735292993";
private static AppController mInstance;
private static ArrayMap<String, Object> objectMap = new ArrayMap<>();
private ArrayList<Activity> list = new ArrayList<>();
private boolean isFinish = false;
//快传文件发送单线程
public static Executor FILE_SENDER_EXECUTOR = Executors.newSingleThreadExecutor();
//快传文件发送主要的线程池
public static Executor MAIN_EXECUTOR = Executors.newFixedThreadPool(5);
// public AppController() {
// super(ShareConstants.TINKER_ENABLE_ALL, "com.gh.base.AppControllerLike",
// "com.tencent.tinker.loader.TinkerLoader", false);
// }
@Override
public void onCreate() {
super.onCreate();
//初始化Fresco
Fresco.initialize(this);
DataUtils.init(this);
HttpsUtils.initHttpsUrlConnection(this);
AppUncaHandler uncaHandler = new AppUncaHandler(this);
Thread.setDefaultUncaughtExceptionHandler(uncaHandler);
mInstance = this;
// 注册push服务注册成功后会向GHPushMessageReceiver发送广播
// 可以从GHPushMessageReceiver的onCommandResult方法中MiPushCommandMessage对象参数中获取注册信息
if (shouldInit()) {
MiPushClient.registerPush(this, APP_ID, APP_KEY);
}
LoggerInterface newLogger = new LoggerInterface() {
@Override
public void setTag(String tag) {
// ignore
}
@Override
public void log(String content, Throwable t) {
Log.d(TAG, content, t);
}
@Override
public void log(String content) {
Log.d(TAG, content);
}
};
Logger.setLogger(this, newLogger);
//友盟推送
final PushAgent mPushAgent = PushAgent.getInstance(this);
//注册推送服务每次调用register方法都会回调该接口
mPushAgent.register(new IUmengRegisterCallback() {
@Override
public void onSuccess(String deviceToken) {
//注册成功会返回device token
Utils.log("deviceToken::" + deviceToken);
//设置别名
mPushAgent.addExclusiveAlias(TokenUtils.getDeviceId(getApplicationContext())
, "GHDID", new UTrack.ICallBack() {
@Override
public void onMessage(boolean b, String s) {
Utils.log("ExclusiveAlias::" + b + "==" + s);
}
});
}
@Override
public void onFailure(String s, String s1) {
Utils.log("deviceToken::" + "注册失败");
}
});
// 友盟推送数据处理
mPushAgent.setNotificationClickHandler(new GHUmengNotificationClickHandler());
// // 监听屏幕状态广播
// if (shouldInit()) {
// UnlockScreenReceiver unlockScreenReceiver = new UnlockScreenReceiver();
// IntentFilter intentFilter = new IntentFilter();
// intentFilter.addAction(Intent.ACTION_SCREEN_ON);
// intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
// registerReceiver(unlockScreenReceiver, intentFilter);
//
// // 用户App运行数据统计服务
// Intent intent = new Intent(getApplicationContext(), AppStaticService.class);
// startService(intent);
//
// AppRunTimeDao dao = new AppRunTimeDao(getApplicationContext());
// for (AppRunTimeInfo appRunTimeInfo : dao.getAll()) {
// Utils.log(appRunTimeInfo.getPackageName() + "====1111=====" + appRunTimeInfo.getRunTime());
// }
// }
}
public static void put(String key, Object object) {
if (objectMap == null) {
objectMap = new ArrayMap<>();
}
objectMap.put(key, object);
}
public static Object get(String key, boolean isRemove) {
if (objectMap == null) {
return null;
}
if (isRemove) {
return objectMap.remove(key);
} else {
return objectMap.get(key);
}
}
public static void remove(String key) {
if (objectMap == null) {
return;
}
objectMap.remove(key);
}
/**
* Activity关闭时删除Activity列表中的Activity对象
*/
public void removeActivity(Activity a) {
if (!isFinish) {
list.remove(a);
Utils.log("remove = " + a.getClass().getSimpleName());
}
}
/**
* 向Activity列表中添加Activity对象
*/
public void addActivity(Activity a) {
Utils.log("add = " + a.getClass().getSimpleName());
list.add(a);
}
/**
* 关闭Activity列表中的所有Activity
*/
public void finishActivity() {
isFinish = true;
for (int i = list.size() - 1; i >= 0; i--) {
Activity activity = list.get(i);
if (null != activity) {
Utils.log("finish = " + activity.getClass().getSimpleName());
activity.finish();
}
}
// 杀死该应用进程
Process.killProcess(Process.myPid());
}
public static String getProcessName(Context cxt, int pid) {
ActivityManager am = (ActivityManager) cxt
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName;
}
}
return null;
}
public static synchronized AppController getInstance() {
return mInstance;
}
private boolean shouldInit() {
ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE));
List<RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
String mainProcessName = getPackageName();
Log.d(TAG, mainProcessName);
int myPid = Process.myPid();
for (RunningAppProcessInfo info : processInfos) {
if (info.pid == myPid && mainProcessName.equals(info.processName)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,51 @@
//package com.gh.base;
//
//
//import android.annotation.TargetApi;
//import android.app.Application;
//import android.content.Context;
//import android.content.Intent;
//import android.os.Build;
//import android.support.multidex.MultiDex;
//
//import com.tencent.tinker.lib.listener.DefaultPatchListener;
//import com.tencent.tinker.lib.listener.PatchListener;
//import com.tencent.tinker.lib.patch.AbstractPatch;
//import com.tencent.tinker.lib.patch.UpgradePatch;
//import com.tencent.tinker.lib.reporter.DefaultLoadReporter;
//import com.tencent.tinker.lib.reporter.DefaultPatchReporter;
//import com.tencent.tinker.lib.reporter.LoadReporter;
//import com.tencent.tinker.lib.reporter.PatchReporter;
//import com.tencent.tinker.lib.tinker.TinkerInstaller;
//import com.tencent.tinker.loader.app.DefaultApplicationLike;
//
//public class AppControllerLike extends DefaultApplicationLike {
//
// public AppControllerLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
// long applicationStartElapsedTime, long applicationStartMillisTime,
// Intent tinkerResultIntent) {
// super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
// applicationStartMillisTime, tinkerResultIntent);
// }
//
// @Override
// public void onBaseContextAttached(Context base) {
// super.onBaseContextAttached(base);
// MultiDex.install(base);
//
// LoadReporter loadReporter = new DefaultLoadReporter(getApplication());
// PatchReporter patchReporter = new DefaultPatchReporter(getApplication());
// PatchListener patchListener = new DefaultPatchListener(getApplication());
// AbstractPatch upgradePatchProcessor = new UpgradePatch();
//
// TinkerInstaller.install(this, loadReporter, patchReporter, patchListener,
// AppTinkerResultService.class, upgradePatchProcessor);
//// TinkerInstaller.install(this);
// }
//
// @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
// public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
// getApplication().registerActivityLifecycleCallbacks(callback);
// }
//
//}

View File

@ -0,0 +1,29 @@
//package com.gh.base;
//
//import com.gh.common.util.Utils;
//import com.tencent.tinker.lib.service.DefaultTinkerResultService;
//import com.tencent.tinker.lib.service.PatchResult;
//import com.tencent.tinker.lib.util.TinkerServiceInternals;
//
//import java.io.File;
//
//
//public class AppTinkerResultService extends DefaultTinkerResultService {
//
// @Override
// public void onPatchResult(PatchResult result) {
// if (result == null) {
// return;
// }
// Utils.log(result);
//
// //first, we want to kill the recover process
// TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
//
// if (result.isSuccess) {
// Utils.log("Tinkder Success");
// deleteRawPatchFile(new File(result.rawPatchFilePath));
// }
// }
//
//}

View File

@ -0,0 +1,128 @@
package com.gh.base;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.gh.common.constant.Config;
import com.gh.common.util.DataCollectionUtils;
import com.gh.common.util.FileUtils;
import com.gh.gamecenter.SplashScreenActivity;
import com.tencent.stat.StatService;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class AppUncaHandler implements UncaughtExceptionHandler {
private UncaughtExceptionHandler mDefaultHandler;
private AppController appController;
public AppUncaHandler(AppController appController) {
// 获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
this.appController = appController;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(appController.getApplicationContext(),
"\"光环助手\"发生错误", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 防止重复奔溃导致助手一直重启20秒内不做处理
SharedPreferences sp = appController.getApplicationContext().getSharedPreferences(
Config.PREFERENCE, Context.MODE_PRIVATE);
long time = sp.getLong("last_restart_time", 0);
if (System.currentTimeMillis() - time > 20 * 1000) {
sp.edit().putLong("last_restart_time", System.currentTimeMillis()).apply();
Intent intent = new Intent(appController.getApplicationContext(),
SplashScreenActivity.class);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent restartIntent = PendingIntent.getActivity(
appController.getApplicationContext(), 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
// 退出程序并重启
AlarmManager mgr = (AlarmManager) appController
.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,
restartIntent); // 1秒钟后重启应用
}
appController.finishActivity();
}
}
// 保存log到本地
private void saveLog(Throwable ex) {
String errorMsg = Log.getStackTraceString(ex);
// MTA主动上传错误
StatService.reportError(appController.getApplicationContext(), errorMsg);
// 上传错误数据
DataCollectionUtils.uploadError(appController.getApplicationContext(), errorMsg);
// 保存到本地
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
File file = new File(FileUtils.getLogPath(appController.getApplicationContext(),
format.format(new Date()) + "_gh_assist" + ".log"));
FileWriter writer = null;
try {
file.createNewFile();
writer = new FileWriter(file);
writer.write(errorMsg);
writer.flush();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @param ex
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
saveLog(ex);
return true;
}
}

View File

@ -1,134 +0,0 @@
package com.gh.base;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.util.Log;
import com.gh.common.constant.Config;
import com.gh.common.util.DataCollectionUtils;
import com.gh.common.util.DataUtils;
import com.gh.gamecenter.SplashScreenActivity;
import com.lightgame.config.CommonDebug;
import com.lightgame.download.FileUtils;
import com.lightgame.utils.AppManager;
import com.lightgame.utils.Utils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
import io.sentry.Sentry;
public class AppUncaughtHandler implements UncaughtExceptionHandler {
private Context mContext;
public AppUncaughtHandler(Context context) {
// 获取系统默认的UncaughtException处理器
mContext = context;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
if (("FinalizerWatchdogDaemon").equals(t.getName())
&& e instanceof TimeoutException) {
// ignore timeoutException
// detail can be found in this didi tech blog post https://mp.weixin.qq.com/s?__biz=MzU1ODEzNjI2NA==&mid=2247487185&idx=2&sn=cf2d9e10053f625bde0f61d246f14870&source=41#wechat_redirect
} else {
new Thread(() -> {
Looper.prepare();
Utils.toast(mContext.getApplicationContext(), "\"光环助手\"发生错误");
Looper.loop();
});
saveLocalLog(mContext, e);
restart(mContext);
Sentry.captureException(e);
}
}
public static void restart(final Context context) {
// S450 这机器会无限重启循环 : (
if ("S450".equals(Build.MODEL)) return;
// 防止重复奔溃导致助手一直重启20秒内不做处理
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long curTime = System.currentTimeMillis();
long time = sp.getLong("last_restart_time", 0);
if (curTime - time > 20 * 1000) {
sp.edit().putLong("last_restart_time", curTime).apply();
Intent intent = new Intent(context, SplashScreenActivity.class);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent restartIntent = PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
// 退出程序并重启
AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, curTime + 3000, restartIntent); // 1秒钟后重启应用
}
//error restart
// System.exit(2);
AppManager.getInstance().finishAllActivity();
}
// 保存log到本地
public static void saveLocalLog(Context context, Throwable ex) {
String errorMsg = Log.getStackTraceString(ex);
Config.setExceptionMsg(errorMsg);
// 保存到本地
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
File file = new File(FileUtils.getLogPath(context.getApplicationContext(),
format.format(new Date()) + "_gh_assist" + ".log"));
FileWriter writer = null;
try {
file.createNewFile();
writer = new FileWriter(file);
writer.write(errorMsg);
writer.flush();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 下次应用启动再上报
*
* @param context
* @param throwable
*/
public static void reportException(Context context, Throwable throwable) {
CommonDebug.logMethodWithParams(context, "ERRMSG", throwable);
// 上传错误数据
try {
DataCollectionUtils.uploadError(context, Log.getStackTraceString(throwable));
} catch (Exception e) {
}
DataUtils.onError(context, throwable);
}
}

View File

@ -1,629 +1,213 @@
package com.gh.base;
import static com.gh.common.util.EntranceUtils.KEY_ENTRANCE;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.TransactionTooLargeException;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import androidx.core.view.LayoutInflaterCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import com.gh.base.fragment.BaseFragment;
import com.gh.common.constant.Constants;
import com.gh.common.tracker.IBusiness;
import com.gh.common.util.DialogHelper;
import com.gh.common.constant.Config;
import com.gh.common.util.DataUtils;
import com.gh.common.util.DialogUtils;
import com.gh.common.util.DisplayUtils;
import com.gh.common.util.EntranceUtils;
import com.gh.common.util.EnvHelper;
import com.gh.common.util.ExtensionsKt;
import com.gh.common.util.MtaHelper;
import com.gh.common.util.NetworkUtils;
import com.gh.common.util.NightModeUtils;
import com.gh.common.util.PackageFlavorHelper;
import com.gh.common.util.PackageInstaller;
import com.gh.common.util.QuickLoginHelper;
import com.gh.common.util.FileUtils;
import com.gh.common.util.PackageUtils;
import com.gh.common.util.RunningUtils;
import com.gh.common.util.SPUtils;
import com.gh.common.util.ShareUtils;
import com.gh.common.util.StringUtils;
import com.gh.common.util.Utils;
import com.gh.download.DownloadManager;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.LoginActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.SplashScreenActivity;
import com.gh.gamecenter.eventbus.EBShowDialog;
import com.lightgame.BaseAppCompatActivity;
import com.lightgame.download.DownloadEntity;
import com.lightgame.download.FileUtils;
import com.lightgame.utils.Utils;
import com.tencent.tauth.Tencent;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
import java.util.List;
import kotlin.Pair;
import pub.devrel.easypermissions.EasyPermissions;
/**
* 只提供基础的服务(EventBus/Share/GlobalDialog/Permissions)
* <p>
* 需要工具栏的页面请继承{@link ToolBarActivity}
*/
public abstract class BaseActivity extends BaseAppCompatActivity implements EasyPermissions.PermissionCallbacks, IBusiness {
// global dialog key
public final static String DOWNLOAD_HIJACK = "hijack";
public final static String LOGIN_EXCEPTION = "loginException";
public final static String PLUGGABLE = "plugin";
public final static String SIGNATURE_CONFLICT = "signature_conflict";
public final static int ID_ROOT_INDICATOR = 999;
public final static int ID_NIGHT_INDICATOR = 998;
public final int MAX_BUNDLE_SIZE = 300;
@NonNull
protected String mEntrance;
private boolean mIsExistLogoutDialog;
protected boolean mNightMode;
public long startPageTime = 0;
protected final Handler mBaseHandler = new BaseHandler(this);
protected static class BaseHandler extends Handler {
private final WeakReference<BaseActivity> mActivityWeakReference;
BaseHandler(BaseActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
BaseActivity activity = mActivityWeakReference.get();
if (activity != null) activity.handleMessage(msg);
}
}
protected void handleMessage(Message msg) {
}
//接收QQ或者QQ空间分享回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ExtensionsKt.tryCatchInRelease(() -> {
if (requestCode == com.tencent.connect.common.Constants.REQUEST_QQ_SHARE
|| requestCode == com.tencent.connect.common.Constants.REQUEST_QZONE_SHARE) {
Tencent.onActivityResultData(requestCode, resultCode, data, ShareUtils.getInstance(this).QqShareListener);
}
return null;
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (PackageFlavorHelper.IS_TEST_FLAVOR && isAutoResetViewBackgroundEnabled()) {
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new CustomLayoutInflaterFactory(this));
}
super.onCreate(savedInstanceState);
if (useEventBus()) EventBus.getDefault().register(this);
mEntrance = getIntent().getStringExtra(KEY_ENTRANCE);
if (TextUtils.isEmpty(mEntrance)) {
mEntrance = Constants.ENTRANCE_UNKNOWN;
}
if (BuildConfig.DEBUG) {
Utils.log("ACTIVITY_ENTRANCE -> " + mEntrance);
}
disableAutofill();
if (savedInstanceState != null) {
String xapkUnzipActivity = SPUtils.getString(Constants.SP_XAPK_UNZIP_ACTIVITY);
String xapkUrl = SPUtils.getString(Constants.SP_XAPK_URL);
Utils.log("页面重建了--" + xapkUnzipActivity + "--" + xapkUrl);
if (this.getClass().isAssignableFrom(SplashScreenActivity.class)) {
SPUtils.setString(Constants.SP_XAPK_UNZIP_ACTIVITY, "");
SPUtils.setString(Constants.SP_XAPK_URL, "");
return;
}
if (this.getClass().getName().equals(xapkUnzipActivity) && !TextUtils.isEmpty(xapkUrl)) {
DownloadEntity downloadEntity = DownloadManager.getInstance().getDownloadEntityByUrl(xapkUrl);
if (downloadEntity != null) {
PackageInstaller.install(this, downloadEntity, false);
SPUtils.setString(Constants.SP_XAPK_UNZIP_ACTIVITY, "");
SPUtils.setString(Constants.SP_XAPK_URL, "");
}
}
}
mNightMode = NightModeUtils.INSTANCE.isNightMode(this);
}
@Override
protected void onResume() {
super.onResume();
startPageTime = System.currentTimeMillis();
if (BuildConfig.IS_NIGHT_MODE_ON
&& !NightModeUtils.INSTANCE.getSystemMode()
&& mNightMode != NightModeUtils.INSTANCE.isNightMode(this)) {
onNightModeChange();
}
}
@SuppressWarnings("ConstantConditions")
@Override
public void setContentView(View view) {
if (!(this instanceof SplashScreenActivity) && PackageFlavorHelper.IS_TEST_FLAVOR) {
view = getRootViewWithEnvIndicator(view);
}
super.setContentView(view);
}
@Override
protected void onDestroy() {
if (useEventBus()) EventBus.getDefault().unregister(this);
mBaseHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
public void toast(String msg) {
Utils.toast(this, msg);
}
public void toast(int msg) {
toast(getString(msg));
}
public void showShare(String url,
String icon,
String shareTitle,
String shareSummary,
ShareUtils.ShareEntrance shareEntrance, String id) {
ShareUtils.getInstance(this).showShareWindows(this,
getWindow().getDecorView(),
url,
icon,
shareTitle,
shareSummary,
shareEntrance, id);
if (shareEntrance == ShareUtils.ShareEntrance.game || shareEntrance == ShareUtils.ShareEntrance.plugin) {
MtaHelper.onEvent("内容分享", "内容分享", shareTitle + shareSummary);
} else {
MtaHelper.onEvent("内容分享", "内容分享", shareTitle);
}
}
/**
* 关闭 editText 自动填充帐号 (我们也用不上),开启的时候有小概率出发 TimeoutException
*/
private void disableAutofill() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getWindow().getDecorView().setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
}
}
private View getRootViewWithEnvIndicator(View view) {
RelativeLayout screenRootView = new RelativeLayout(this);
screenRootView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
LinearLayout ll = new LinearLayout(this);
TextView tv = new TextView(this);
String envText = "正式环境";
tv.setBackground(ContextCompat.getDrawable(this, R.color.theme));
if (EnvHelper.isDevEnv()) {
envText = "测试环境";
tv.setBackground(ContextCompat.getDrawable(this, R.color.theme_red));
}
tv.setText(envText);
tv.setGravity(Gravity.CENTER);
tv.setTextColor(Color.WHITE);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
tv.measure(0, 0);
tv.setAlpha(0.15F);
tv.setId(ID_ROOT_INDICATOR);
int height = tv.getMeasuredHeight();
int width = tv.getMeasuredWidth();
tv.setPadding(DisplayUtils.dip2px(20), 0, DisplayUtils.dip2px(20), 0);
ll.setTranslationX(DisplayUtils.dip2px(20));
ll.setRotation(45);
ll.addView(tv);
ll.setPadding(0, (width - height) / 2, 0, (width - height) / 2);
if (BuildConfig.DEBUG) {
tv.setOnLongClickListener(v -> {
EntranceUtils.saveShortcut(this.getClass().getName(), getIntent().getExtras());
return true;
});
}
screenRootView.addView(view);
screenRootView.addView(ll);
if (BuildConfig.IS_NIGHT_MODE_ON) {
screenRootView.addView(getNightModeIndicatorView());
}
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) ll.getLayoutParams();
lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
view.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
return screenRootView;
}
private View getNightModeIndicatorView() {
LinearLayout ll = new LinearLayout(this);
TextView tv = new TextView(this);
String envText = NightModeUtils.INSTANCE.isNightMode(this) ? "夜间模式" : "日间模式";
tv.setBackground(ContextCompat.getDrawable(this, R.color.theme));
tv.setText(envText);
tv.setGravity(Gravity.CENTER);
tv.setTextColor(Color.WHITE);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
tv.measure(0, 0);
tv.setAlpha(NightModeUtils.INSTANCE.isNightMode(this) ? 0.8F : 0.15F);
tv.setId(ID_NIGHT_INDICATOR);
int height = tv.getMeasuredHeight();
int width = tv.getMeasuredWidth();
tv.setPadding(DisplayUtils.dip2px(20), 0, DisplayUtils.dip2px(20), 0);
ll.setTranslationX(DisplayUtils.dip2px(-20));
ll.setRotation(-45);
ll.addView(tv);
ll.setPadding(0, (width - height) / 2, 0, (width - height) / 2);
if (PackageFlavorHelper.IS_TEST_FLAVOR) {
tv.setOnClickListener(v -> {
//切换深色模式
String mode;
String positive;
String negative;
if (NightModeUtils.INSTANCE.getSystemMode()) {
mode = "跟随系统模式";
positive = "普通模式";
negative = "深色模式";
} else if (NightModeUtils.INSTANCE.getNightMode()) {
mode = "深色模式";
positive = "跟随系统模式";
negative = "普通模式";
} else {
mode = "普通模式";
positive = "跟随系统模式";
negative = "深色模式";
}
DialogHelper.showCenterDialog(this, "选择模式", "当前为 " + mode, positive, negative, () -> {
if (NightModeUtils.INSTANCE.getSystemMode()) {
NightModeUtils.INSTANCE.setNightMode(false);
NightModeUtils.INSTANCE.setSystemMode(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
} else {
NightModeUtils.INSTANCE.setSystemMode(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}
}
NightModeUtils.INSTANCE.initNightMode();
}, () -> {
if (NightModeUtils.INSTANCE.getSystemMode()) {
NightModeUtils.INSTANCE.setNightMode(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}
} else {
boolean nightMode = NightModeUtils.INSTANCE.getNightMode();
NightModeUtils.INSTANCE.setNightMode(!NightModeUtils.INSTANCE.getNightMode());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getDelegate().setLocalNightMode(nightMode ? AppCompatDelegate.MODE_NIGHT_NO : AppCompatDelegate.MODE_NIGHT_YES);
}
}
NightModeUtils.INSTANCE.setSystemMode(false);
NightModeUtils.INSTANCE.initNightMode();
});
});
}
return ll;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(final EBShowDialog showDialog) {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
&& this.getClass().getName().equals(RunningUtils.getTopActivity(this))) {
if (DOWNLOAD_HIJACK.equals(showDialog.getType())) {
DialogUtils.showQqSessionDialog(this);// 建议用户联系客服
} else if (PLUGGABLE.equals(showDialog.getType())) {
DialogHelper.showPluginDialog(this, () -> {
if (FileUtils.isEmptyFile(showDialog.getPath())) {
toast(R.string.install_failure_hint);
} else {
PackageInstaller.uninstall(BaseActivity.this, showDialog.getPath());
}
return null;
});
} else if (SIGNATURE_CONFLICT.equals(showDialog.getType())) {
DialogHelper.showSignatureConflictDialog(this, () -> {
PackageInstaller.uninstall(BaseActivity.this, showDialog.getPath());
return null;
});
} else if (LOGIN_EXCEPTION.equals(showDialog.getType())) {
if (mIsExistLogoutDialog) return;
mIsExistLogoutDialog = true;
try {
JSONObject object = new JSONObject(showDialog.getPath());
JSONObject device = object.getJSONObject("device");
String model = device.getString("model");
DialogHelper.showCenterDialog(this, "你的账号已在另外一台设备登录"
, StringUtils.buildString("", model, "")
, "知道了", "重新登录"
, () -> {
}
, () -> {
if (SPUtils.getBoolean(Constants.SP_HAS_GET_PHONE_INFO) || NetworkUtils.isOpenMobileData(BaseActivity.this)) {
QuickLoginHelper.startLogin(BaseActivity.this, "你的账号已在另外一台设备登录多设备-重新登录");
} else {
startActivity(LoginActivity.getIntent(BaseActivity.this,
"你的账号已在另外一台设备登录多设备-重新登录"));
}
}
);
mBaseHandler.postDelayed(() -> mIsExistLogoutDialog = false, 5000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onPause() {
super.onPause();
if (isFinishing()) {
onFinish();
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
if (fragment.isAdded() && fragment instanceof BaseFragment) {
((BaseFragment) fragment).onParentActivityFinish();
}
}
}
}
/**
* 此回调可用于确认当前 activity 已经执行了 finish() 方法并处于 isFinishing 状态
*/
protected void onFinish() {
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsDenied(int requestCode, List<String> perms) {
}
@Override
public void onPermissionsGranted(int requestCode, List<String> perms) {
}
protected void setStatusBarColor(int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(color);
}
}
/**
* 提供当前 activity 的中文名 (不重载的话为类名)
*/
public String getActivityNameInChinese() {
return getClass().getSimpleName();
}
/**
* @param entrance 上一个页面的链式入口名称
* @param path 当前页面名称
* @return 完整的链式入口名称
*/
public static String mergeEntranceAndPath(String entrance, String path) {
if (TextUtils.isEmpty(entrance) && TextUtils.isEmpty(path)) return "";
if (TextUtils.isEmpty(entrance) && !TextUtils.isEmpty(path)) {
return StringUtils.buildString("(", path, ")");
}
if (!TextUtils.isEmpty(entrance) && TextUtils.isEmpty(path)) {
return entrance;
}
return StringUtils.buildString(entrance, "+(", path, ")");
}
protected boolean useEventBus() {
return true;
}
@Override
public Resources getResources() {
Resources resources = super.getResources();
if (resources.getConfiguration().fontScale != 1.0f) {
Configuration configuration = resources.getConfiguration();
configuration.fontScale = 1.0f;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
}
return resources;
}
/**
* ActivityThread每次调用onSaveInstanceState时outState大小都会累加最终会导致{@link TransactionTooLargeException}异常
* 解决方案判断每次获取到的outState大小当达到300k时手动clear掉
*/
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (preventRecreateFragmentByFragmentManager()) {
outState = discardFragmentFromSaveInstanceState(outState);
}
long bundleSize = getBundleSize(outState);
if (bundleSize > MAX_BUNDLE_SIZE * 1024) {
outState.clear();
}
}
/**
* 是否停用 Activity 重建时 FragmentManager 根据 saveState 自动重建保存的 Fragment 的功能
*/
protected boolean preventRecreateFragmentByFragmentManager() {
return false;
}
private Bundle discardFragmentFromSaveInstanceState(Bundle outState) {
if (outState != null) {
outState.remove("android:support:fragments");
}
return outState;
}
private long getBundleSize(Bundle bundle) {
long dataSize;
Parcel obtain = Parcel.obtain();
try {
obtain.writeBundle(bundle);
dataSize = obtain.dataSize();
} finally {
obtain.recycle();
}
return dataSize;
}
@Override
public Pair<String, String> getBusinessId() {
return null;
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
onNightModeChange();
}
protected void onNightModeChange() {
if (BuildConfig.IS_NIGHT_MODE_ON) {
mNightMode = NightModeUtils.INSTANCE.isNightMode(this);
TextView tv = findViewById(ID_NIGHT_INDICATOR);
if (tv != null) {
tv.setText(NightModeUtils.INSTANCE.isNightMode(this) ? "夜间模式" : "日间模式");
tv.setAlpha(NightModeUtils.INSTANCE.isNightMode(this) ? 0.8F : 0.15F);
}
if (isAutoResetViewBackgroundEnabled()) {
updateStaticViewBackground(getWindow().getDecorView());
}
}
}
protected boolean isAutoResetViewBackgroundEnabled() {
return false;
}
/**
* 自动重置部分 view 的背景颜色/资源
*
* @param view 父 view
*/
private void updateStaticViewBackground(View view) {
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
View child = ((ViewGroup) view).getChildAt(i);
updateStaticViewBackground(child);
}
}
String backgroundString = (String) view.getTag(CustomLayoutInflaterFactory.TAG_BACKGROUND_ID);
String textColorString = (String) view.getTag(CustomLayoutInflaterFactory.TAG_TEXT_COLOR_ID);
if (backgroundString != null) {
if (backgroundString.startsWith("#")) return;
int backgroundId = Integer.parseInt(
backgroundString
.replace("@", "")
.replace("?", "")
);
if (backgroundId != 0) {
try {
TypedValue value = new TypedValue();
getResources().getValue(backgroundId, value, true);
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
view.setBackgroundColor(ExtensionsKt.toColor(backgroundId, this));
} else {
view.setBackground(ExtensionsKt.toDrawable(backgroundId, this));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (textColorString != null && view instanceof TextView) {
if (textColorString.startsWith("#")) return;
int textColorId = Integer.parseInt(
textColorString
.replace("@", "")
.replace("?", "")
);
if (textColorId != 0) {
try {
final ColorStateList colorStateList = ContextCompat.getColorStateList(this, textColorId);
((TextView) view).setTextColor(colorStateList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
import com.gh.gamecenter.listener.OnCallBackListener;
import com.gh.gamecenter.manager.SystemBarTintManager;
import com.gh.gamecenter.manager.SystemBarTintManager.SystemBarConfig;
import java.util.ArrayList;
import butterknife.ButterKnife;
import de.greenrobot.event.EventBus;
public class BaseActivity extends Activity implements OnCallBackListener {
private SystemBarTintManager tintManager;
protected String entrance;
private boolean isPause;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Utils.log(this.getClass().getSimpleName());
AppController.getInstance().addActivity(this);
EventBus.getDefault().register(this);
entrance = getIntent().getStringExtra("entrance");
if (getIntent().getBundleExtra("data") != null) {
entrance = getIntent().getBundleExtra("data").getString("entrance");
}
}
protected void init(View contentView, String title) {
init(contentView);
TextView actionbar_tv_title = (TextView) findViewById(R.id.actionbar_tv_title);
actionbar_tv_title.setText(title);
}
protected void init(View contentView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setTheme(R.style.AppTheme);
setTranslucentStatus(true);
tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(R.color.theme);
SystemBarConfig config = tintManager.getConfig();
contentView.setPadding(0, config.getPixelInsetTop(false), 0,
config.getPixelInsetBottom());
}
setContentView(contentView);
ButterKnife.bind(this);
int actionbar_height = getSharedPreferences(Config.PREFERENCE,
Context.MODE_PRIVATE).getInt("actionbar_height",
DisplayUtils.dip2px(getApplicationContext(), 48));
RelativeLayout reuse_actionbar = (RelativeLayout) findViewById(R.id.reuse_actionbar);
LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, actionbar_height);
reuse_actionbar.setLayoutParams(lparams);
findViewById(R.id.actionbar_rl_back).setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
protected SystemBarTintManager getTintManager() {
return tintManager;
}
@Override
public void finish() {
super.finish();
AppController.getInstance().removeActivity(this);
}
public void toast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
public void toast(int msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@TargetApi(19)
protected void setTranslucentStatus(boolean status) {
Window window = getWindow();
WindowManager.LayoutParams winParams = window.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (status) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
window.setAttributes(winParams);
}
//如果是游戏分享newsTitle默认为空
public void showShare(String url, String gameName, String icon, String newsTitle, ArrayList<String> tag) {
//判断是否是官方版
boolean isPlugin = false;
if (tag != null){
for (String s : tag) {
if (!"官方版".equals(s)){
isPlugin = true;
}
}
}
ShareUtils.getInstance(this).showShareWindows(new View(this), url, gameName, icon, newsTitle, isPlugin, true);
if (newsTitle == null) {
DataUtils.onEvent(this, "内容分享", gameName);
} else {
DataUtils.onEvent(this, "内容分享", newsTitle);
}
}
public void onEventMainThread(final EBShowDialog showDialog) {
if (!isPause && this.getClass().getName().equals(RunningUtils.getTopActivity(this))) {
if ("hijack".equals(showDialog.getType())) {
DialogUtils.showQqSessionDialog(this, null);// 建议用户联系客服
} else if ("plugin".equals(showDialog.getType())) {
DialogUtils.showPluginDialog(this, new DialogUtils.ConfiremListener(){
@Override
public void onConfirem() {
if (FileUtils.isEmptyFile(showDialog.getPath())) {
Toast.makeText(BaseActivity.this, "解析包出错(可能被误删了),请重新下载", Toast.LENGTH_SHORT).show();
} else {
startActivity(PackageUtils.getUninstallIntent(BaseActivity.this, showDialog.getPath()));
}
}
});
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Override
protected void onPause() {
super.onPause();
DataUtils.onPause(this);
isPause = true;
}
@Override
protected void onResume() {
super.onResume();
DataUtils.onResume(this);
isPause = false;
DownloadManager.getInstance(this).initGameMap();
}
@Override
public void loadDone() {
}
@Override
public void loadDone(Object obj) {
}
@Override
public void loadError() {
}
@Override
public void loadEmpty() {
}
}

View File

@ -1,155 +0,0 @@
package com.gh.base;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import com.gh.base.adapter.FragmentAdapter;
import com.gh.base.fragment.BaseFragment_TabLayout;
import com.gh.common.view.TabIndicatorView;
import com.gh.gamecenter.R;
import com.google.android.material.tabs.TabLayout;
import com.lightgame.view.NoScrollableViewPager;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
/**
* Created by khy on 15/03/18.
*/
public abstract class BaseActivity_TabLayout extends ToolBarActivity implements ViewPager.OnPageChangeListener {
public static final String PAGE_INDEX = "PAGE_INDEX";
protected TabLayout mTabLayout;
protected NoScrollableViewPager mViewPager;
protected TabIndicatorView mTabIndicatorView;
protected View mDividerLineView;
protected List<Fragment> mFragmentsList;
protected List<String> mTabTitleList;
protected int mCheckedIndex = 0;
protected abstract void initFragmentList(List<Fragment> fragments);
protected abstract void initTabTitleList(List<String> tabTitleList);
protected int provideIndicatorWidth() {
return 20;
}
protected View provideTabView(int position, String tabTitle) {
return null;
}
@Override
protected int getLayoutId() {
return R.layout.activity_tablayout_viewpager;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTabLayout = findViewById(R.id.activity_tab_layout);
mViewPager = findViewById(R.id.activity_view_pager);
mTabIndicatorView = findViewById(R.id.activity_tab_indicator);
mDividerLineView = findViewById(R.id.dividerLine);
if (getIntent() != null) mCheckedIndex = getIntent().getIntExtra(PAGE_INDEX, 0);
mTabTitleList = new ArrayList<>();
initTabTitleList(mTabTitleList);
mFragmentsList = new ArrayList<>(restoreFragments());
if (mFragmentsList.isEmpty() || mFragmentsList.size() != mTabTitleList.size()) {
mFragmentsList.clear();
initFragmentList(mFragmentsList);
}
mViewPager.setOffscreenPageLimit(mFragmentsList.size());
mViewPager.addOnPageChangeListener(this);
mViewPager.setAdapter(new FragmentAdapter(getSupportFragmentManager(), mFragmentsList, mTabTitleList));
mViewPager.setCurrentItem(mCheckedIndex);
mTabLayout.setupWithViewPager(mViewPager);
mTabIndicatorView.setupWithTabLayout(mTabLayout);
mTabIndicatorView.setupWithViewPager(mViewPager);
mTabIndicatorView.setIndicatorWidth(provideIndicatorWidth());
for (int i = 0; i < mTabLayout.getTabCount(); i++) {
TabLayout.Tab tab = mTabLayout.getTabAt(i);
if (tab == null) continue;
String tabTitle = tab.getText() != null ? tab.getText().toString() : "";
View tabView = provideTabView(i, tabTitle);
if (tabView == null)
tabView = BaseFragment_TabLayout.createDefaultTabCustomView(this, tabTitle);
tab.setCustomView(tabView);
}
BaseFragment_TabLayout.initTabStyle(mTabLayout, mCheckedIndex);
}
private ArrayList<Fragment> restoreFragments() {
String tag = "android:switcher:" + mViewPager.getId() + ":";
ArrayList<Fragment> fragments = new ArrayList<>();
int childCount = mTabTitleList.size();
for (int index = 0; index < childCount; index++) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag + index);
if (fragment != null) {
fragments.add(fragment);
}
}
return fragments;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
protected void onNightModeChange() {
super.onNightModeChange();
View container = findViewById(R.id.activity_tab_container);
if (container != null) {
container.setBackgroundColor(ContextCompat.getColor(this, R.color.background_white));
for (int i = 0; i < mTabLayout.getTabCount(); i++) {
TabLayout.Tab tab = mTabLayout.getTabAt(i);
if (tab != null) {
BaseFragment_TabLayout.updateTabStyle(tab, tab.isSelected());
}
}
}
if (mDividerLineView != null) {
mDividerLineView.setBackgroundColor(ContextCompat.getColor(this, R.color.divider));
}
}
}

View File

@ -0,0 +1,392 @@
package com.gh.base;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.gh.common.constant.Config;
import com.gh.common.util.DataUtils;
import com.gh.common.util.DialogUtils;
import com.gh.common.util.DisplayUtils;
import com.gh.common.util.FileUtils;
import com.gh.common.util.GameUtils;
import com.gh.common.util.NetworkUtils;
import com.gh.common.util.PackageUtils;
import com.gh.common.util.ShareUtils;
import com.gh.common.view.DownloadDialog;
import com.gh.download.DataWatcher;
import com.gh.download.DownloadEntity;
import com.gh.download.DownloadManager;
import com.gh.gamecenter.DownloadManagerActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.entity.ApkEntity;
import com.gh.gamecenter.entity.GameEntity;
import com.gh.gamecenter.eventbus.EBDownloadStatus;
import com.gh.gamecenter.eventbus.EBPackage;
import com.gh.gamecenter.manager.PackageManager;
import com.tencent.tauth.Tencent;
/**
* Created by Administrator on 2016/9/19.
* 游戏详情、新闻详情基类(控制底部下载栏)
*/
public abstract class BaseDetailActivity extends BaseActivity implements View.OnClickListener {
protected TextView actionbar_tv_title;
protected RecyclerView detail_rv_show;
protected LinearLayout detail_ll_bottom;
protected TextView detail_tv_download;
protected ProgressBar detail_pb_progressbar;
protected TextView detail_tv_per;
protected LinearLayout reuse_ll_loading;
protected LinearLayout reuse_no_connection;
protected LinearLayout reuse_none_data;
protected TextView reuse_tv_none_data;
protected ImageView iv_share;
protected GameEntity gameEntity;
protected DownloadEntity mDownloadEntity;
protected String entrance;
protected String name;
protected String title;
protected String downloadAddWord;
protected String downloadOffText;
protected Handler handler = new Handler();
private DataWatcher dataWatcher = new DataWatcher() {
@Override
public void onDataChanged(DownloadEntity downloadEntity) {
if (gameEntity != null && gameEntity.getApk().size() == 1) {
String url = gameEntity.getApk().get(0).getUrl();
if (url.equals(downloadEntity.getUrl())) {
if (!"pause".equals(DownloadManager.getInstance(BaseDetailActivity.this).
getStatus(downloadEntity.getUrl()))) {
mDownloadEntity = downloadEntity;
invalidate();
}
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
entrance = getIntent().getStringExtra("entrance");
if (getIntent().getBundleExtra("data") != null) {
entrance = getIntent().getBundleExtra("data").getString("entrance");
}
View contentView = View.inflate(this, R.layout.activity_detail, null);
// 添加分享图标
iv_share = new ImageView(this);
iv_share.setImageResource(R.drawable.ic_share);
iv_share.setOnClickListener(this);
iv_share.setVisibility(View.GONE);
iv_share.setPadding(DisplayUtils.dip2px(this, 13),DisplayUtils.dip2px(this, 11)
,DisplayUtils.dip2px(this, 11),DisplayUtils.dip2px(this, 13));
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
DisplayUtils.dip2px(this, 48), DisplayUtils.dip2px(this, 48));
params.addRule( RelativeLayout.CENTER_VERTICAL);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT );
RelativeLayout reuse_actionbar = (RelativeLayout) contentView.findViewById(
R.id.reuse_actionbar);
reuse_actionbar.addView(iv_share, params);
init(contentView);
actionbar_tv_title = (TextView) findViewById(R.id.actionbar_tv_title);
detail_rv_show = (RecyclerView) findViewById(R.id.detail_rv_show);
detail_ll_bottom = (LinearLayout) findViewById(R.id.detail_ll_bottom);
detail_tv_download = (TextView) findViewById(R.id.detail_tv_download);
detail_pb_progressbar = (ProgressBar) findViewById(R.id.detail_pb_progressbar);
detail_tv_per = (TextView) findViewById(R.id.detail_tv_per);
reuse_ll_loading = (LinearLayout) findViewById(R.id.reuse_ll_loading);
reuse_no_connection = (LinearLayout) findViewById(R.id.reuse_no_connection);
reuse_none_data = (LinearLayout) findViewById(R.id.reuse_none_data);
reuse_tv_none_data = (TextView) findViewById(R.id.reuse_tv_none_data);
detail_ll_bottom.setOnClickListener(this);
detail_tv_download.setOnClickListener(this);
detail_pb_progressbar.setOnClickListener(this);
detail_tv_per.setOnClickListener(this);
reuse_no_connection.setOnClickListener(this);
}
//接收QQ或者QQ空间分享回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == com.tencent.connect.common.Constants.REQUEST_QQ_SHARE
|| requestCode == com.tencent.connect.common.Constants.REQUEST_QZONE_SHARE) {
Tencent.onActivityResultData(requestCode, resultCode, data, ShareUtils.getInstance(this).QqShareListener);
}
}
@Override
protected void onResume() {
super.onResume();
if (gameEntity != null
&& gameEntity.getApk() != null
&& gameEntity.getApk().size() == 1) {
initDownload(true);
}
DownloadManager.getInstance(this).addObserver(dataWatcher);
}
@Override
protected void onPause() {
super.onPause();
DownloadManager.getInstance(this).removeObserver(dataWatcher);
}
protected void initDownload(boolean isCheck) {
if (Config.isShow(this)) {
detail_ll_bottom.setVisibility(View.VISIBLE);
detail_rv_show.setPadding(0, 0, 0,
DisplayUtils.dip2px(getApplicationContext(), 60));
} else {
detail_ll_bottom.setVisibility(View.GONE);
detail_rv_show.setPadding(0, 0, 0, 0);
}
if (gameEntity != null && "光环助手".equals(gameEntity.getName())) {
detail_ll_bottom.setVisibility(View.GONE);
detail_rv_show.setPadding(0, 0, 0, 0);
} else if (gameEntity == null || gameEntity.getApk().isEmpty()) {
detail_tv_download.setVisibility(View.VISIBLE);
detail_pb_progressbar.setVisibility(View.GONE);
detail_tv_per.setVisibility(View.GONE);
if (TextUtils.isEmpty(downloadOffText)) {
detail_tv_download.setText("暂无下载");
} else {
detail_tv_download.setText(downloadOffText);
}
detail_tv_download.setBackgroundResource(R.drawable.game_item_btn_pause_style);
detail_tv_download.setTextColor(0xFF999999);
detail_tv_download.setClickable(false);
} else {
detail_tv_download.setVisibility(View.VISIBLE);
detail_pb_progressbar.setVisibility(View.GONE);
detail_tv_per.setVisibility(View.GONE);
boolean isInstalled = false;
if (gameEntity.getApk() != null && gameEntity.getApk().size() == 1
&& PackageManager.isInstalled(gameEntity.getApk().get(0).getPackageName())) {
isInstalled = true;
}
if (isInstalled) {
if (PackageManager.isCanUpdate(gameEntity.getId(), gameEntity.getApk().get(0).getPackageName())) {
if (TextUtils.isEmpty(downloadAddWord)) {
detail_tv_download.setBackgroundResource(
R.drawable.game_item_btn_download_style);
detail_tv_download.setText(String.format("更新《%s》",
gameEntity.getName()));
} else {
detail_tv_download.setBackgroundResource(
R.drawable.game_item_btn_download_style);
detail_tv_download.setText(String.format("更新《%s》%s",
gameEntity.getName(), downloadAddWord));
}
} else {
if (gameEntity.getTag() != null && gameEntity.getTag().size() != 0
&& !TextUtils.isEmpty(gameEntity.getApk().get(0).getGhVersion())
&& !PackageUtils.isSignature(this, gameEntity.getApk().get(0).getPackageName())) {
if (TextUtils.isEmpty(downloadAddWord)) {
detail_tv_download.setBackgroundResource(
R.drawable.game_item_btn_plugin_style);
detail_tv_download.setText(String.format("插件化《%s》",
gameEntity.getName()));
} else {
detail_tv_download.setBackgroundResource(
R.drawable.game_item_btn_plugin_style);
detail_tv_download.setText(String.format("插件化《%s》%s",
gameEntity.getName(), downloadAddWord));
}
} else {
if (TextUtils.isEmpty(downloadAddWord)) {
detail_tv_download.setBackgroundResource(
R.drawable.game_item_btn_launch_style);
detail_tv_download.setText(String.format("启动《%s》",
gameEntity.getName()));
} else {
detail_tv_download.setBackgroundResource(
R.drawable.game_item_btn_launch_style);
detail_tv_download.setText(String.format("启动《%s》%s",
gameEntity.getName(), downloadAddWord));
}
}
}
} else {
String status = GameUtils.getDownloadBtnText(this, gameEntity);
if ("插件化".equals(status)) {
detail_tv_download.setBackgroundResource(R.drawable.game_item_btn_plugin_style);
} else if ("打开".equals(status)) {
detail_tv_download.setBackgroundResource(R.drawable.game_item_btn_launch_style);
} else {
detail_tv_download.setBackgroundResource(R.drawable.game_item_btn_download_style);
}
if (TextUtils.isEmpty(downloadAddWord)) {
detail_tv_download.setText(String.format(status + "《%s》",
gameEntity.getName()));
} else {
detail_tv_download.setText(String.format(status + "《%s》%s",
gameEntity.getName(), downloadAddWord));
}
}
}
if (isCheck && gameEntity != null
&& gameEntity.getApk() != null
&& gameEntity.getApk().size() == 1) {
String url = gameEntity.getApk().get(0).getUrl();
DownloadEntity downloadEntity = DownloadManager.getInstance(getApplicationContext()).get(url);
if (downloadEntity != null) {
mDownloadEntity = downloadEntity;
detail_tv_download.setVisibility(View.GONE);
detail_pb_progressbar.setVisibility(View.VISIBLE);
detail_tv_per.setVisibility(View.VISIBLE);
invalidate();
}
}
}
private void invalidate() {
detail_pb_progressbar.setProgress((int) (mDownloadEntity.getPercent() * 10));
detail_tv_per.setTextColor(0xFFFFFFFF);
switch (mDownloadEntity.getStatus()) {
case downloading:
case pause:
case timeout:
case neterror:
case waiting:
detail_tv_per.setText("下载中");
break;
case done:
detail_tv_per.setText("安装");
if (mDownloadEntity.isPluggable()
&& PackageManager.isInstalled(mDownloadEntity.getPackageName())) {
detail_pb_progressbar.setProgressDrawable(getResources().getDrawable(R.drawable.progressbar_plugin_radius_style));
} else {
detail_pb_progressbar.setProgressDrawable(getResources().getDrawable(R.drawable.progressbar_normal_radius_style));
}
break;
case cancel:
case hijack:
case notfound:
initDownload(false);
break;
default:
break;
}
}
// 接收下载被删除消息
public void onEvent(EBDownloadStatus status) {
if ("delete".equals(status.getStatus())
&& gameEntity != null
&& gameEntity.getApk() != null
&& gameEntity.getApk().size() == 1) {
String url = gameEntity.getApk().get(0).getUrl();
if (url.equals(status.getUrl())) {
initDownload(false);
}
}
}
// 接受安装、卸载消息
public void onEventMainThread(EBPackage busFour) {
if (gameEntity != null
&& gameEntity.getApk() != null
&& gameEntity.getApk().size() == 1) {
String packageName = gameEntity.getApk().get(0).getPackageName();
if (packageName.equals(busFour.getPackageName())) {
initDownload(false);
}
}
}
@Override
public void onClick(View v) {
if (v == detail_tv_download) {
if (gameEntity != null && !gameEntity.getApk().isEmpty()) {
if (gameEntity.getApk().size() == 1) {
if (NetworkUtils.isWifiConnected(this)) {
download();
} else {
DialogUtils.showDownloadDialog(this, new DialogUtils.ConfiremListener() {
@Override
public void onConfirem() {
download();
}
});
}
} else {
DownloadDialog.getInstance(this).showPopupWindow(v, gameEntity, entrance, name + ":" + title);
}
} else {
toast("稍等片刻~!游戏正在上传中...");
}
} else if (v == detail_pb_progressbar || v == detail_tv_per) {
String str = detail_tv_per.getText().toString();
if ("下载中".equals(str)) {
Intent intent = new Intent(this, DownloadManagerActivity.class);
intent.putExtra("currentItem", 1);
intent.putExtra("url", gameEntity.getApk().get(0).getUrl());
intent.putExtra("entrance", entrance + "+(" + name + "[" + title + "])");
startActivity(intent);
} else if ("安装".equals(str)) {
PackageUtils.launchSetup(this, mDownloadEntity.getPath());
}
}
}
private void download() {
String str = detail_tv_download.getText().toString();
if (str.contains("启动")) {
DataUtils.onGameLaunchEvent(this, gameEntity.getName(), gameEntity.getApk().get(0).getPlatform(), name);
PackageUtils.launchApplicationByPackageName(this, gameEntity.getApk().get(0).getPackageName());
} else {
String method;
if (str.contains("更新")) {
method = "更新";
} else if (str.contains("插件化")) {
method = "插件化";
} else {
method = "下载";
}
ApkEntity apkEntity = gameEntity.getApk().get(0);
String msg = FileUtils.isCanDownload(this, apkEntity.getSize());
if (TextUtils.isEmpty(msg)) {
DataUtils.onGameDownloadEvent(this, gameEntity.getName(), apkEntity.getPlatform(), entrance, "下载开始");
DownloadManager.createDownload(this, apkEntity, gameEntity, method, entrance, name + ":" + title);
detail_tv_download.setVisibility(View.GONE);
detail_pb_progressbar.setVisibility(View.VISIBLE);
detail_tv_per.setVisibility(View.VISIBLE);
detail_pb_progressbar.setProgress(0);
detail_tv_per.setText("0.0%");
DownloadManager.getInstance(BaseDetailActivity.this).putStatus(apkEntity.getUrl(), "downloading");
} else {
toast(msg);
}
}
}
}

View File

@ -0,0 +1,93 @@
package com.gh.base;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.gh.gamecenter.listener.OnCallBackListener;
import butterknife.ButterKnife;
import de.greenrobot.event.EventBus;
/**
* Created by LGT on 2016/9/4.
* Fragment 基类
*/
public class BaseFragment extends Fragment implements OnCallBackListener {
protected View view;
protected Handler handler = new Handler();
protected boolean isEverpause;
protected void init(int layout) {
view = View.inflate(getActivity(), layout, null);
ButterKnife.bind(this, view);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isEverpause = false;
EventBus.getDefault().register(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if(container != null){
container.removeView(view);
}
return view;
}
public boolean isEverpause() {
return isEverpause;
}
@Override
public void onPause() {
super.onPause();
isEverpause = true;
}
@Override
public void onResume() {
super.onResume();
isEverpause = false;
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Override
public void loadDone() {
}
@Override
public void loadDone(Object obj) {
}
@Override
public void loadError() {
}
@Override
public void loadEmpty() {
}
}

View File

@ -0,0 +1,185 @@
package com.gh.base;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.gh.common.constant.Config;
import com.gh.common.util.DataUtils;
import com.gh.common.util.DialogUtils;
import com.gh.common.util.DisplayUtils;
import com.gh.common.util.FileUtils;
import com.gh.common.util.PackageUtils;
import com.gh.common.util.RunningUtils;
import com.gh.common.util.ShareUtils;
import com.gh.common.util.Utils;
import com.gh.download.DownloadManager;
import com.gh.gamecenter.R;
import com.gh.gamecenter.eventbus.EBShowDialog;
import com.gh.gamecenter.manager.SystemBarTintManager;
import com.gh.gamecenter.manager.SystemBarTintManager.SystemBarConfig;
import java.util.ArrayList;
import butterknife.ButterKnife;
import de.greenrobot.event.EventBus;
public class BaseFragmentActivity extends FragmentActivity {
protected String entrance;
private boolean isPause;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Utils.log(this.getClass().getSimpleName());
AppController.getInstance().addActivity(this);
EventBus.getDefault().register(this);
entrance = getIntent().getStringExtra("entrance");
if (getIntent().getBundleExtra("data") != null) {
entrance = getIntent().getBundleExtra("data").getString("entrance");
}
}
public void init(View contentView, String title) {
init(contentView);
TextView actionbar_tv_title = (TextView) findViewById(R.id.actionbar_tv_title);
actionbar_tv_title.setText(title);
}
public void init(View contentView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setTheme(R.style.AppTheme);
setTranslucentStatus(true);
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(R.color.theme);
SystemBarConfig config = tintManager.getConfig();
contentView.setPadding(0, config.getPixelInsetTop(false), 0,
config.getPixelInsetBottom());
}
setContentView(contentView);
ButterKnife.bind(this);
int actionbar_height = getSharedPreferences(Config.PREFERENCE,
Context.MODE_PRIVATE).getInt("actionbar_height",
DisplayUtils.dip2px(getApplicationContext(), 48));
RelativeLayout reuse_actionbar = (RelativeLayout) findViewById(R.id.reuse_actionbar);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, actionbar_height);
reuse_actionbar.setLayoutParams(params);
findViewById(R.id.actionbar_rl_back).setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
@Override
public void finish() {
super.finish();
AppController.getInstance().removeActivity(this);
}
public void toast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
public void toast(int msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@TargetApi(19)
protected void setTranslucentStatus(boolean status) {
Window window = getWindow();
WindowManager.LayoutParams winParams = window.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (status) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
window.setAttributes(winParams);
}
//如果是游戏分享newsTitle默认为空
public void showShare(String url, String gameName, String icon, String newsTitle, ArrayList<String> tag) {
//判断是否是官方版
boolean isPlugin = false;
if (tag != null){
for (String s : tag) {
if (!"官方版".equals(s)){
isPlugin = true;
}
}
}
ShareUtils.getInstance(this).showShareWindows(new View(this), url, gameName, icon, newsTitle, isPlugin, true);
if (newsTitle == null){
DataUtils.onEvent(this, "内容分享", gameName);
}else {
DataUtils.onEvent(this, "内容分享", newsTitle);
}
}
public void onEventMainThread(final EBShowDialog showDialog) {
if (!isPause && this.getClass().getName().equals(RunningUtils.getTopActivity(this))) {
if ("hijack".equals(showDialog.getType())) {
DialogUtils.showQqSessionDialog(this, null);// 建议用户联系客服
} else if ("plugin".equals(showDialog.getType())) {
DialogUtils.showPluginDialog(this, new DialogUtils.ConfiremListener(){
@Override
public void onConfirem() {
if (FileUtils.isEmptyFile(showDialog.getPath())) {
Toast.makeText(BaseFragmentActivity.this, "解析包出错(可能被误删了),请重新下载", Toast.LENGTH_SHORT).show();
} else {
startActivity(PackageUtils.getUninstallIntent(BaseFragmentActivity.this, showDialog.getPath()));
}
}
});
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Override
protected void onPause() {
super.onPause();
DataUtils.onPause(this);
isPause = true;
}
@Override
protected void onResume() {
super.onResume();
DataUtils.onResume(this);
isPause = false;
DownloadManager.getInstance(this).initGameMap();
}
}

View File

@ -1,54 +0,0 @@
package com.gh.base;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
/**
* 目前仅提供butterknife bind方法
*
* @author CsHeng
* @Date 16/06/2017
* @Time 9:55 AM
*/
public abstract class BaseRecyclerViewHolder<T> extends RecyclerView.ViewHolder implements View.OnClickListener {
private T mData;
private OnListClickListener mListClickListener;
public BaseRecyclerViewHolder(View itemView) {
super(itemView);
}
/**
* 具体的设置监听在childViewHolder 设置
*
* @param itemView
* @param data 一般情况下只传列表数据
* @param listClickListener 列表事件接口
*/
public BaseRecyclerViewHolder(View itemView, T data, OnListClickListener listClickListener) {
this(itemView);
this.mData = data;
this.mListClickListener = listClickListener;
}
public BaseRecyclerViewHolder(View itemView, OnListClickListener listClickListener) {
this(itemView);
this.mListClickListener = listClickListener;
}
public void setClickData(T clickData) {
this.mData = clickData;
}
@Override
public void onClick(View view) {
try {
mListClickListener.onListClick(view, getAdapterPosition(), mData);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -1,761 +0,0 @@
package com.gh.base
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.webkit.JavascriptInterface
import android.widget.CheckBox
import android.widget.FrameLayout
import android.widget.TextView
import androidx.lifecycle.Observer
import com.gh.common.AppExecutor
import com.gh.common.runOnIoThread
import com.gh.common.util.*
import com.gh.common.view.RichEditor
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.qa.editor.*
import com.gh.gamecenter.qa.entity.AnswerEntity
import com.gh.gamecenter.qa.entity.ArticleEntity
import com.gh.gamecenter.qa.entity.EditorInsertEntity
import com.gh.gamecenter.video.poster.PosterEditActivity
import com.gh.gamecenter.video.upload.UploadManager
import com.google.gson.JsonObject
import com.halo.assistant.HaloApp
import com.lightgame.utils.Util_System_Keyboard
import com.lightgame.utils.Utils
import com.lightgame.view.CheckableImageView
import io.reactivex.disposables.Disposable
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
abstract class BaseRichEditorActivity<VM : BaseRichEditorViewModel> : ToolBarActivity(),
KeyboardHeightObserver, UploadVideoListener {
lateinit var mRichEditor: RichEditor
private lateinit var mEditorTextNumTv: TextView
private lateinit var mEditorFont: CheckableImageView
private lateinit var mEditorLink: CheckableImageView
private lateinit var mEditorFontBold: CheckableImageView
private lateinit var mEditorFontItalic: CheckableImageView
private lateinit var mEditorFontStrikeThrough: CheckableImageView
private lateinit var mEditorFontUnderline: CheckableImageView
private lateinit var mEditorParagraphH1: CheckableImageView
private lateinit var mEditorParagraphH2: CheckableImageView
private lateinit var mEditorParagraphH3: CheckableImageView
private lateinit var mEditorParagraphH4: CheckableImageView
private lateinit var mEditorParagraphQuote: CheckableImageView
private lateinit var mEditorFontContainer: View
private lateinit var mEditorParagraphContainer: View
private lateinit var mEditorLinkContainer: View
private lateinit var mEditorInsertDetailContainer: View
private lateinit var mTagsContainer: FrameLayout
private lateinit var mUploadVideoGuideContainer: View
protected lateinit var mOriginalCb: CheckBox
private lateinit var mOriginalTipsContainer: View
private lateinit var mOriginalTipsClose: TextView
private var mCurrentParagraphStyle = ""
private var mIsExtendedKeyboardShow = false
private var mAgreePostPic: Boolean = false
private var mGuideDisposable: Disposable? = null
protected lateinit var mViewModel: VM
protected var mIsKeyBoardShow = false
private var mKeyboardHeightProvider: KeyboardHeightProvider? = null
private var mMaxUploadVideoGuideCount = 2
val FILE_HOST = "file:///"
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
DialogUtils.fixWebViewKeyboardNotWorking(this)
if (resultCode != Activity.RESULT_OK) return
val insertData: EditorInsertEntity?
when (requestCode) {
INSERT_ANSWER_CODE -> {
val answer =
data?.getParcelableExtra<AnswerEntity>(AnswerEntity::class.java.simpleName)
if (answer != null) {
mRichEditor.focusEditor()
insertData = EditorInsertEntity.transform(answer)
mRichEditor.insertCustomStyleLink(insertData)
}
}
INSERT_ARTICLE_CODE -> {
val article =
data?.getParcelableExtra<ArticleEntity>(ArticleEntity::class.java.simpleName)
if (article != null) {
mRichEditor.focusEditor()
insertData = EditorInsertEntity.transform(article)
mRichEditor.insertCustomStyleLink(insertData)
}
}
INSERT_GAME_CODE -> {
val game = data?.getParcelableExtra<GameEntity>(GameEntity::class.java.simpleName)
if (game != null) {
mRichEditor.focusEditor()
insertData = EditorInsertEntity.transform(game)
mRichEditor.insertCustomStyleLink(insertData)
}
}
INSERT_GAME_COLLECTION_CODE -> {
val gameCollectionEntity = data?.getParcelableExtra<GamesCollectionEntity>(GamesCollectionEntity::class.java.simpleName)
if (gameCollectionEntity != null) {
mRichEditor.focusEditor()
insertData = EditorInsertEntity.transform(gameCollectionEntity)
mRichEditor.insertCustomStyleLink(insertData)
}
}
REQUEST_CODE_IMAGE -> {
if (data != null) mViewModel.uploadPic(data)
}
INSERT_MEDIA_VIDEO_CODE -> {
val localVideoList = data?.getParcelableArrayListExtra<LocalVideoEntity>(LocalVideoEntity::class.java.name) ?: arrayListOf()
if (localVideoList.isNotEmpty()) {
mRichEditor.focusEditor()
uploadVideo(localVideoList)
}
}
REQUEST_CODE_IMAGE_CROP -> {
val imagePath = data?.getStringExtra(CropImageActivity.RESULT_CLIP_PATH)
if (!imagePath.isNullOrEmpty()) {
mViewModel.uploadPoster(imagePath)
}
}
INSERT_VIDEO_CODE -> {
val videoEntity = data?.getParcelableExtra<MyVideoEntity>(MyVideoEntity::class.java.simpleName)
if (videoEntity != null) {
mRichEditor.focusEditor()
insertData = EditorInsertEntity.transform(videoEntity)
mRichEditor.insertCustomStyleLink(insertData)
}
}
}
closeExtendedKeyboard()
AppExecutor.uiExecutor.executeWithDelay(Runnable {
Util_System_Keyboard.showSoftKeyboard(this)
}, 100)
}
private fun uploadVideo(localVideoList: ArrayList<LocalVideoEntity>) {
mViewModel.localVideoList.addAll(localVideoList)
runOnIoThread {
localVideoList.forEach {
if (it.poster.startsWith("http")) {
runOnUiThread {
mRichEditor.focusEditor()
mRichEditor.insertPlaceholderVideo(it.id, it.poster)
}
} else {
val videoThumbnail = BitmapUtils.getVideoThumbnail(it.filePath)
val filePath = "${cacheDir.absolutePath}${File.separator}${it.id}.webp"
BitmapUtils.saveBitmap(videoThumbnail, filePath)
it.poster = filePath
runOnUiThread {
mRichEditor.focusEditor()
mRichEditor.insertPlaceholderVideo(it.id, "$FILE_HOST${it.poster}")
}
}
}
mViewModel.uploadVideo()
}
}
private fun findView() {
mRichEditor = findViewById(R.id.rich_editor)
mEditorTextNumTv = findViewById(R.id.editorTextNumTv)
mEditorFont = findViewById(R.id.editor_font)
mEditorLink = findViewById(R.id.editor_link)
mEditorFontBold = findViewById(R.id.editor_font_bold)
mEditorFontItalic = findViewById(R.id.editor_font_italic)
mEditorFontStrikeThrough = findViewById(R.id.editor_font_strikethrough)
mEditorFontUnderline = findViewById(R.id.editor_font_underline)
mEditorParagraphH1 = findViewById(R.id.editor_paragraph_h1)
mEditorParagraphH2 = findViewById(R.id.editor_paragraph_h2)
mEditorParagraphH3 = findViewById(R.id.editor_paragraph_h3)
mEditorParagraphH4 = findViewById(R.id.editor_paragraph_h4)
mEditorParagraphQuote = findViewById(R.id.editor_paragraph_quote)
mEditorFontContainer = findViewById(R.id.editor_font_container)
mEditorParagraphContainer = findViewById(R.id.editor_paragraph_container)
mEditorLinkContainer = findViewById(R.id.editor_link_container)
mEditorInsertDetailContainer = findViewById(R.id.editor_insert_detail_container)
mTagsContainer = findViewById(R.id.tagsContainer)
mUploadVideoGuideContainer = findViewById(R.id.uploadVideoGuideContainer)
mOriginalCb = findViewById(R.id.originalCb)
mOriginalTipsContainer = findViewById(R.id.originalTipsContainer)
mOriginalTipsClose = findViewById(R.id.originalTipsClose)
}
@SuppressLint("AddJavascriptInterface", "ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findView()
onRichClick()
mViewModel = provideViewModel()
mViewModel.setUploadVideoListener(this)
mKeyboardHeightProvider = KeyboardHeightProvider(this)
mRichEditor.post { mKeyboardHeightProvider?.start() }
// 防止个别手机在Js里无法获取粘贴内容
mRichEditor.addJavascriptInterface(OnPasteListener(), "onPasteListener")
mRichEditor.addJavascriptInterface(OnCursorChangeListener(), "OnCursorChangeListener")
mRichEditor.addJavascriptInterface(
OnEditorTextChangeListener(),
"OnEditorTextChangeListener"
)
mRichEditor.addJavascriptInterface(OnVideoListener(), "onVideoListener")
mRichEditor.addJavascriptInterface(
OnQuoteCountChangeListener(),
"OnQuoteCountChangeListener"
)
mRichEditor.setInputEnabled(true)
mRichEditor.setPadding(16, 12, 16, 12)
mRichEditor.setOnTouchListener { _, _ ->
if (mIsExtendedKeyboardShow) {
closeExtendedKeyboard()
Util_System_Keyboard.showSoftKeyboard(this)
//是否消费事件根据mRichEditor是否含有焦点决定mRichEditor没有焦点则不消费事件
mRichEditor.hasFocus()
} else false
}
mOriginalCb.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
mOriginalTipsContainer.alpha = 0f
mOriginalTipsContainer.visibility = View.VISIBLE
ObjectAnimator.ofFloat(mOriginalTipsContainer, "alpha", 0f, 1f).setDuration(200).start()
}
}
observeData()
}
private fun observeData() {
mViewModel.chooseImagesUpload.observe(this, Observer {
mRichEditor.focusEditor()
for (key in it.keys) {
mRichEditor.insertPlaceholderImage(key)
}
})
mViewModel.chooseImagesUploadSuccess.observe(this, Observer {
val jsonArray = JSONArray()
for (key in it.keys) {
val jsonObject = JSONObject()
jsonObject.put("id", key)
jsonObject.put("url", it[key])
jsonArray.put(jsonObject)
}
mRichEditor.replacePlaceholderImage(jsonArray.toString())
})
}
override fun onKeyboardHeightChanged(height: Int, orientation: Int) {
mIsKeyBoardShow = height > 0
if (height > 0) {
closeExtendedKeyboard()
}
}
fun closeExtendedKeyboard() {
mEditorInsertDetailContainer.visibility = View.GONE
mEditorFont.isChecked = false
mEditorLink.isChecked = false
mIsExtendedKeyboardShow = false
}
protected fun controlEditorInsertContainerEnabled(isEnabled: Boolean) {
mEditorFont.isEnabled = isEnabled
}
private fun onRichClick() {
mEditorFont.setOnClickListener {
controlEditorFontContainer()
}
mEditorLink.setOnClickListener {
controlEditorLinkContainer()
}
mEditorFontBold.setOnClickListener {
mEditorFontBold.isChecked = !mEditorFontBold.isChecked
mRichEditor.setBold()
if (mEditorFontBold.isChecked) {
MtaHelper.onEvent(mtaEventName(), "文字样式", "文字样式-加粗")
}
}
mEditorFontItalic.setOnClickListener {
mEditorFontItalic.isChecked = !mEditorFontItalic.isChecked
mRichEditor.setItalic()
if (mEditorFontItalic.isChecked) {
MtaHelper.onEvent(mtaEventName(), "文字样式", "文字样式-斜体")
}
}
mEditorFontStrikeThrough.setOnClickListener {
mEditorFontStrikeThrough.isChecked = !mEditorFontStrikeThrough.isChecked
mRichEditor.setStrikeThrough()
if (mEditorFontStrikeThrough.isChecked) {
MtaHelper.onEvent(mtaEventName(), "文字样式", "文字样式-删除线")
}
}
mEditorFontUnderline.setOnClickListener {
mEditorFontUnderline.isChecked = !mEditorFontUnderline.isChecked
mRichEditor.setUnderline()
if (mEditorFontUnderline.isChecked) {
MtaHelper.onEvent(mtaEventName(), "文字样式", "文字样式-下滑线")
}
}
mEditorParagraphH1.setOnClickListener {
if (mEditorParagraphH1.isChecked) {
mRichEditor.formatBlock()
} else {
MtaHelper.onEvent(mtaEventName(), "段落样式", "段落样式-1级标题")
mRichEditor.setHeading(1)
}
mEditorParagraphH1.isChecked = !mEditorParagraphH1.isChecked
}
mEditorParagraphH2.setOnClickListener {
if (mEditorParagraphH2.isChecked) {
mRichEditor.formatBlock()
} else {
MtaHelper.onEvent(mtaEventName(), "段落样式", "段落样式-2级标题")
mRichEditor.setHeading(2)
}
mEditorParagraphH2.isChecked = !mEditorParagraphH2.isChecked
}
mEditorParagraphH3.setOnClickListener {
if (mEditorParagraphH3.isChecked) {
mRichEditor.formatBlock()
} else {
MtaHelper.onEvent(mtaEventName(), "段落样式", "段落样式-3级标题")
mRichEditor.setHeading(3)
}
mEditorParagraphH3.isChecked = !mEditorParagraphH3.isChecked
}
mEditorParagraphH4.setOnClickListener {
if (mEditorParagraphH4.isChecked) {
mRichEditor.formatBlock()
} else {
MtaHelper.onEvent(mtaEventName(), "段落样式", "段落样式-4级标题")
mRichEditor.setHeading(4)
}
mEditorParagraphH4.isChecked = !mEditorParagraphH4.isChecked
}
mEditorParagraphQuote.setOnClickListener {
if (mEditorParagraphQuote.isChecked) {
mRichEditor.formatBlock()
} else {
MtaHelper.onEvent(mtaEventName(), "段落样式", "段落样式-引用")
mRichEditor.setBlockquote()
}
mEditorParagraphQuote.isChecked = !mEditorParagraphQuote.isChecked
}
findViewById<View>(R.id.editor_link_answer).setOnClickListener {
MtaHelper.onEvent(mtaEventName(), "插入链接", "插入链接-回答")
startActivityForResult(
InsertAnswerWrapperActivity.getIntent(this),
INSERT_ANSWER_CODE
)
}
findViewById<View>(R.id.editor_link_article).setOnClickListener {
MtaHelper.onEvent(mtaEventName(), "插入链接", "插入链接-文章")
startActivityForResult(
InsertArticleWrapperActivity.getIntent(this),
INSERT_ARTICLE_CODE
)
}
findViewById<View>(R.id.editor_link_game).setOnClickListener {
MtaHelper.onEvent(mtaEventName(), "插入链接", "插入链接-游戏")
startActivityForResult(
GameActivity.getIntent(this, GameActivity.INSERT_GAME_TITLE),
INSERT_GAME_CODE
)
}
findViewById<View>(R.id.editor_link_video).setOnClickListener {
startActivityForResult(
InsertVideoWrapperActivity.getIntent(this),
INSERT_VIDEO_CODE
)
}
findViewById<View>(R.id.editor_link_game_collection).setOnClickListener {
startActivityForResult(
InsertGameCollectionWrapperActivity.getIntent(this),
INSERT_GAME_COLLECTION_CODE
)
}
findViewById<View>(R.id.editor_video).setOnClickListener {
chooseVideo()
}
findViewById<View>(R.id.editor_image).setOnClickListener {
if (!mAgreePostPic && !NetworkUtils.isWifiOr4GOr3GConnected(this)) {
mAgreePostPic = true
DialogHelper.showDialog(
this,
"警告",
"当前使用移动网络,上传图片会消耗手机流量",
"我知道了", "", { chooseImage() },
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
return@setOnClickListener
}
chooseImage()
NewLogUtils.logChooseMedia(
"view_media",
if (mtaEventName() == "提问帖") "提问帖" else "帖子",
"图片"
)
}
findViewById<View>(R.id.uploadVideoGuideClose).setOnClickListener {
hideUploadVideoGuide()
if (mGuideDisposable != null && !mGuideDisposable!!.isDisposed) {
mGuideDisposable!!.dispose()
mGuideDisposable = null
}
}
findViewById<View>(R.id.originalTipsClose).setOnClickListener {
val animator = ObjectAnimator.ofFloat(mOriginalTipsContainer, "alpha", 1f, 0f).setDuration(200)
animator.doOnEnd {
mOriginalTipsContainer.visibility = View.GONE
}
animator.start()
}
}
private fun chooseVideo() {
MtaHelper.onEvent(mtaEventName(), "插入链接", "插入链接-视频")
val videoCount = mViewModel.quoteCountEntity.videoCount
if (videoCount >= MAX_MEDIA_COUNT) {
toast(R.string.answer_edit_max_video_hint)
return
}
try {
PermissionHelper.checkStoragePermissionBeforeAction(this,
object : EmptyCallback {
override fun onCallback() {
val maxChooseCount = if (videoCount + 3 <= MAX_MEDIA_COUNT) 3 else MAX_MEDIA_COUNT - videoCount
startActivityForResult(
LocalMediaActivity.getIntent(
this@BaseRichEditorActivity,
LocalMediaActivity.ChooseType.VIDEO,
maxChooseCount,
if (mtaEventName() == "提问帖") "发提问帖" else "发帖子"
), INSERT_MEDIA_VIDEO_CODE
)
NewLogUtils.logChooseMedia(
"view_media",
if (mtaEventName() == "提问帖") "提问帖" else "帖子",
"视频"
)
}
})
} catch (e: Exception) {
toast(R.string.media_image_hint)
e.printStackTrace()
}
}
private fun chooseImage() {
MtaHelper.onEvent(mtaEventName(), "插入图片", "插入图片")
val imageCount = mViewModel.quoteCountEntity.imageCount
if (imageCount >= MAX_MEDIA_COUNT) {
toast(R.string.answer_edit_max_img_hint)
return
}
try {
PermissionHelper.checkStoragePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
val maxChooseCount = if (imageCount + 10 <= MAX_MEDIA_COUNT) 10 else MAX_MEDIA_COUNT - imageCount
val intent = LocalMediaActivity.getIntent(
this@BaseRichEditorActivity,
LocalMediaActivity.ChooseType.IMAGE,
maxChooseCount,
if (mtaEventName() == "提问帖") "发提问帖" else "发帖子"
)
startActivityForResult(intent, REQUEST_CODE_IMAGE)
}
})
} catch (e: Exception) {
toast(R.string.media_image_hint)
e.printStackTrace()
}
}
private fun controlEditorFontContainer() {
mEditorFont.isChecked = !mEditorFont.isChecked
mEditorLink.isChecked = false
val isShouldDelay = if (mEditorFont.isChecked) {
Util_System_Keyboard.hideSoftKeyboard(this)
true
} else {
Util_System_Keyboard.showSoftKeyboard(this)
false
}
mEditorInsertDetailContainer.postDelayed({
mEditorInsertDetailContainer.visibility =
if (mEditorFont.isChecked) View.VISIBLE else View.GONE
mEditorFontContainer.visibility = if (mEditorFont.isChecked) View.VISIBLE else View.GONE
mEditorParagraphContainer.visibility =
if (mEditorFont.isChecked) View.VISIBLE else View.GONE
mEditorLinkContainer.visibility = View.GONE
mTagsContainer.visibility = View.GONE
mIsExtendedKeyboardShow = mEditorFont.isChecked
}, if (isShouldDelay) 200 else 0L)
}
private fun controlEditorLinkContainer() {
mEditorLink.isChecked = !mEditorLink.isChecked
mEditorFont.isChecked = false
val isShouldDelay = if (mEditorLink.isChecked) {
Util_System_Keyboard.hideSoftKeyboard(this)
true
} else {
Util_System_Keyboard.showSoftKeyboard(this)
false
}
mEditorInsertDetailContainer.postDelayed({
mEditorInsertDetailContainer.visibility =
if (mEditorLink.isChecked) View.VISIBLE else View.GONE
mEditorLinkContainer.visibility = if (mEditorLink.isChecked) View.VISIBLE else View.GONE
mEditorFontContainer.visibility = View.GONE
mEditorParagraphContainer.visibility = View.GONE
mTagsContainer.visibility = View.GONE
mIsExtendedKeyboardShow = mEditorLink.isChecked
}, if (isShouldDelay) 200 else 0L)
}
override fun handleBackPressed(): Boolean {
if (mIsExtendedKeyboardShow) {
closeExtendedKeyboard()
return true
}
return super.handleBackPressed()
}
override fun onResume() {
super.onResume()
mKeyboardHeightProvider?.setKeyboardHeightObserver(this)
}
override fun onPause() {
super.onPause()
mKeyboardHeightProvider?.setKeyboardHeightObserver(null)
}
//视频上传功能引导
fun showUploadVideoGuide() {
mUploadVideoGuideContainer.postDelayed({
val count = SPUtils.getInt(getVideoGuideKey(), 0)
if (count >= mMaxUploadVideoGuideCount) return@postDelayed
mUploadVideoGuideContainer.alpha = 0f
mUploadVideoGuideContainer.visibility = View.VISIBLE
mUploadVideoGuideContainer.animate().alpha(1f).setDuration(200).start()
mGuideDisposable = countDownTimer(3) { finish, _ ->
if (finish) {
hideUploadVideoGuide()
}
}
SPUtils.setInt(getVideoGuideKey(), count + 1)
}, 1000)
}
fun hideUploadVideoGuide() {
val animate = mUploadVideoGuideContainer.animate().alpha(0f).setDuration(200)
animate.doOnEnd {
mUploadVideoGuideContainer.visibility = View.GONE
}
animate.start()
}
override fun onDestroy() {
super.onDestroy()
mKeyboardHeightProvider?.close()
val path = mViewModel.currentUploadingVideo?.filePath
if (path != null && UploadManager.isUploading(path)) {
UploadManager.cancelTask(path)
}
if (mGuideDisposable != null && !mGuideDisposable!!.isDisposed) {
mGuideDisposable!!.dispose()
mGuideDisposable = null
}
}
private inner class OnCursorChangeListener {
@JavascriptInterface
fun onElements(elements: String) {
Utils.log("-----------------------")
Utils.log(elements)
Utils.log(mRichEditor.html)
Utils.log("-----------------------")
mCurrentParagraphStyle = when {
elements.contains(ELEMENT_PARAGRAPH_QUOTE) -> ELEMENT_PARAGRAPH_QUOTE
elements.contains(ELEMENT_PARAGRAPH_P) -> ELEMENT_PARAGRAPH_P
else -> ""
}
mBaseHandler.post {
mEditorFontBold.isChecked = elements.contains(ELEMENT_NAME_BOLD)
mEditorFontItalic.isChecked = elements.contains(ELEMENT_NAME_ITALIC)
mEditorFontStrikeThrough.isChecked = elements.contains(ELEMENT_NAME_STRIKE)
mEditorFontUnderline.isChecked = elements.contains(ELEMENT_NAME_UNDERLINE)
mEditorParagraphH1.isChecked = elements.contains(ELEMENT_PARAGRAPH_H1)
mEditorParagraphH2.isChecked = elements.contains(ELEMENT_PARAGRAPH_H2)
mEditorParagraphH3.isChecked = elements.contains(ELEMENT_PARAGRAPH_H3)
mEditorParagraphH4.isChecked = elements.contains(ELEMENT_PARAGRAPH_H4)
mEditorParagraphQuote.isChecked = elements.contains(ELEMENT_PARAGRAPH_QUOTE)
}
}
}
private inner class OnPasteListener {
@JavascriptInterface
fun onPaste() {
val clipboard =
HaloApp.getInstance().application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipText = clipboard.text.toString()
if (!TextUtils.isEmpty(clipText)) {
// 替换换行符号否则 插入失败
val text = clipText.replace("[ ]".toRegex(), "&nbsp;")
.replace("[\r\n]".toRegex(), "<br/>")
mBaseHandler.post { mRichEditor.insertHtml(text) }
}
}
}
private inner class OnEditorTextChangeListener {
@JavascriptInterface
fun onTextChange(count: Int) {
val num = if (count > MAX_INPUT_TEXT_NUM) MAX_INPUT_TEXT_NUM - count else count
mEditorTextNumTv.post {
mEditorTextNumTv.text = num.toString()
mViewModel.quoteCountEntity.textCount = num
}
}
}
private inner class OnQuoteCountChangeListener {
@JavascriptInterface
fun onQuoteCountChange(
imageCount: Int,
articleCount: Int,
answerCount: Int,
videoCount: Int,
gameCount: Int
) {
mEditorTextNumTv.post {
mViewModel.quoteCountEntity.apply {
this.imageCount = imageCount
this.articleCount = articleCount
this.answerCount = answerCount
this.videoCount = videoCount
this.gameCount = gameCount
}
}
}
}
private inner class OnVideoListener {
@JavascriptInterface
fun showDeleteDialog(id: String) {
DialogHelper.showDialog(this@BaseRichEditorActivity, "提示", "确定删除吗?", "确定", "取消", {
runOnUiThread {
mRichEditor.delPlaceholderVideo(id)
mViewModel.deleteVideo(id)
}
})
}
@JavascriptInterface
fun updatePoster(id: String, videoId: String, url: String) {
mViewModel.id = id
mViewModel.videoId = videoId
val videoEntity = VideoEntity(url = url)
val intent =
PosterEditActivity.getIntentByVideo(this@BaseRichEditorActivity, videoEntity)
startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP)
}
@JavascriptInterface
fun deleteUploadingVideo(id: String) {
mViewModel.deleteVideo(id)
}
@JavascriptInterface
fun reUploadVideo(id: String) {
val video = mViewModel.uploadVideoErrorList.find { it.id == id }
if (video != null) {
mViewModel.localVideoList.add(video)
mViewModel.uploadVideoErrorList.remove(video)
mViewModel.uploadVideo()
}
}
}
override fun insertPlaceholderVideo(id: String, poster: String) {
mRichEditor.insertPlaceholderVideo(id, poster)
}
override fun updateVideoProgress(id: String, progress: String) {
mRichEditor.updateVideoProgress(id, progress)
}
override fun videoUploadFinished(id: String, url: String, msg: JsonObject) {
try {
val obj = JSONObject()
obj.put("poster", msg.get("poster").asString)
obj.put("url", msg.get("url").asString)
obj.put("duration", RichEditor.formatVideoDuration(msg.get("length").asLong))
obj.put("id", msg.get("_id").asString)
obj.put("status", "pending")
mRichEditor.videoUploadFinished(id, url, obj.toString())
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
override fun changePoster(id: String, poster: String) {
mRichEditor.changePoster(id, poster)
}
override fun videoUploadFailed(id: String) {
mRichEditor.videoUploadFailed(id)
}
open fun getSelectedLabel(): Int = 0
open fun onActivityDialogResult(requestCode: Int, resultCode: Int, data: Intent?) {}
abstract fun mtaEventName(): String
abstract fun provideViewModel(): VM
abstract fun getVideoGuideKey(): String
companion object {
const val ELEMENT_NAME_BOLD = " b "
const val ELEMENT_NAME_ITALIC = " i "
const val ELEMENT_NAME_STRIKE = " strike "
const val ELEMENT_NAME_UNDERLINE = " u "
const val ELEMENT_PARAGRAPH_H1 = " h1 "
const val ELEMENT_PARAGRAPH_H2 = " h2 "
const val ELEMENT_PARAGRAPH_H3 = " h3 "
const val ELEMENT_PARAGRAPH_H4 = " h4 "
const val ELEMENT_PARAGRAPH_P = " p "
const val ELEMENT_PARAGRAPH_QUOTE = " blockquote "
const val INSERT_ANSWER_CODE = 411
const val INSERT_ARTICLE_CODE = 412
const val INSERT_GAME_CODE = 413
const val INSERT_GAME_COLLECTION_CODE = 414
const val INSERT_VIDEO_CODE = 415
const val MAX_INPUT_TEXT_NUM = 10000
const val MAX_MEDIA_COUNT = 20
const val REQUEST_CODE_IMAGE = 120
const val INSERT_MEDIA_VIDEO_CODE = 121
const val REQUEST_CODE_IMAGE_CROP = 122
}
}

Some files were not shown because too many files have changed in this diff Show More