将推送代码接入主工程
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
// 基于socket长链的push推送
|
||||
|
||||
project.dependencies {
|
||||
implementation rootProject.ext.dependencies.modulepushbase
|
||||
launcherImplementation rootProject.ext.dependencies.modulepush
|
||||
independentImplementation rootProject.ext.dependencies.modulepushnoop
|
||||
if (Boolean.valueOf(RELEASE)) {
|
||||
implementation rootProject.ext.dependencies.modulepushbase
|
||||
launcherImplementation rootProject.ext.dependencies.modulepush
|
||||
independentImplementation rootProject.ext.dependencies.modulepushnoop
|
||||
} else {
|
||||
implementation project(":modules:mogo-module-push-base")
|
||||
launcherImplementation project(":modules:mogo-module-push")
|
||||
independentImplementation project(":modules:mogo-module-push-noop")
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,8 @@ public class MogoApplication extends AbsMogoApplication {
|
||||
MogoModulePaths.addModule( new MogoModule( TanluConstants.TAG, TanluConstants.MODEL_NAME ) );
|
||||
MogoModulePaths.addModule( new MogoModule( MogoServicePaths.PATH_SHARE, "ShareControl" ) );
|
||||
|
||||
MogoModulePaths.addModule( new MogoModule( EventPanelConstants.PATH_NAME,
|
||||
EventPanelConstants.MODULE_NAME ) );
|
||||
MogoModulePaths.addModule( new MogoModule( LeftPanelConst.PATH_NAME,
|
||||
LeftPanelConst.MODULE_NAME ) );
|
||||
MogoModulePaths.addModule( new MogoModule( EventPanelConstants.PATH_NAME, EventPanelConstants.MODULE_NAME ) );
|
||||
MogoModulePaths.addModule( new MogoModule( LeftPanelConst.PATH_NAME, LeftPanelConst.MODULE_NAME ) );
|
||||
|
||||
MogoModulePaths.addBaseModule( new MogoModule( ServiceConst.PATH_REFRESH_STRATEGY, ServiceConst.PATH_REFRESH_STRATEGY ) );
|
||||
MogoModulePaths.addBaseModule( new MogoModule( V2XConst.PATH_V2X_UI, V2XConst.PATH_V2X_UI ) );
|
||||
|
||||
@@ -172,5 +172,14 @@ targetSdkVersion : 22,
|
||||
// 基础服务实现
|
||||
mogobaseservicesdk : "com.mogo.base:services-sdk:${MOGO_BASE_SERVICES_SDK_VERSION}",
|
||||
mogobaseserviceapk : "com.mogo.base:services-apk:${MOGO_BASE_SERVICES_APK_VERSION}",
|
||||
|
||||
// google
|
||||
googlezxing : "com.google.zxing:core:3.3.3",
|
||||
litezxing : "com.google.zxing:litezxing:1.0.29.8",
|
||||
|
||||
// android - room
|
||||
androidxroomruntime : "androidx.room:room-runtime:2.2.3",
|
||||
androidxroomcompiler : "androidx.room:room-compiler:2.2.3",
|
||||
androidxroomktx : "androidx.room:room-ktx:2.2.3",
|
||||
]
|
||||
}
|
||||
1
modules/mogo-module-push-base/.gitignore
vendored
Normal file
1
modules/mogo-module-push-base/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
47
modules/mogo-module-push-base/build.gradle
Normal file
47
modules/mogo-module-push-base/build.gradle
Normal file
@@ -0,0 +1,47 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode Integer.valueOf(VERSION_CODE)
|
||||
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
kapt {
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
0
modules/mogo-module-push-base/consumer-rules.pro
Normal file
0
modules/mogo-module-push-base/consumer-rules.pro
Normal file
3
modules/mogo-module-push-base/gradle.properties
Normal file
3
modules/mogo-module-push-base/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
GROUP=com.mogo.module
|
||||
POM_ARTIFACT_ID=module-push-base
|
||||
VERSION_CODE=1
|
||||
21
modules/mogo-module-push-base/proguard-rules.pro
vendored
Normal file
21
modules/mogo-module-push-base/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.module.push.base"></manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.mogo.module.push.base;
|
||||
|
||||
public class PushUIConstants {
|
||||
public static final String NAME = "PUSH_UI";
|
||||
public static final String PATH = "/push/ui";
|
||||
public static final String Push_MESSAGE_ACTIVITY_PATH = "/push/ui/message"; //消息列表activity
|
||||
}
|
||||
1
modules/mogo-module-push-noop/.gitignore
vendored
Normal file
1
modules/mogo-module-push-noop/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
4
modules/mogo-module-push-noop/README.md
Normal file
4
modules/mogo-module-push-noop/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# 基于 socketserver 实现的push推送(空)
|
||||
|
||||
---
|
||||
目前,仅 launcher 实现推送,独立 app 不用
|
||||
57
modules/mogo-module-push-noop/build.gradle
Normal file
57
modules/mogo-module-push-noop/build.gradle
Normal file
@@ -0,0 +1,57 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode Integer.valueOf(VERSION_CODE)
|
||||
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
kapt {
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// 小智语音,免唤醒词等服务
|
||||
compileOnly rootProject.ext.dependencies.mogoserviceapi
|
||||
compileOnly rootProject.ext.dependencies.arouter
|
||||
kapt rootProject.ext.dependencies.aroutercompiler
|
||||
|
||||
if( Boolean.valueOf(RELEASE) ){
|
||||
implementation rootProject.ext.dependencies.modulepushbase
|
||||
} else {
|
||||
implementation project(":modules:mogo-module-push-base")
|
||||
}
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
0
modules/mogo-module-push-noop/consumer-rules.pro
Normal file
0
modules/mogo-module-push-noop/consumer-rules.pro
Normal file
3
modules/mogo-module-push-noop/gradle.properties
Normal file
3
modules/mogo-module-push-noop/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
GROUP=com.mogo.module
|
||||
POM_ARTIFACT_ID=module-push-noop
|
||||
VERSION_CODE=1
|
||||
21
modules/mogo-module-push-noop/proguard-rules.pro
vendored
Normal file
21
modules/mogo-module-push-noop/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.module.push.noop"></manifest>
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.mogo.module.push.noop;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.alibaba.android.arouter.facade.annotation.Route;
|
||||
import com.mogo.map.listener.IMogoMapListener;
|
||||
import com.mogo.map.location.IMogoLocationListener;
|
||||
import com.mogo.map.marker.IMogoMarkerClickListener;
|
||||
import com.mogo.map.navi.IMogoNaviListener;
|
||||
import com.mogo.module.push.base.PushUIConstants;
|
||||
import com.mogo.service.module.IMogoModuleLifecycle;
|
||||
import com.mogo.service.module.IMogoModuleProvider;
|
||||
|
||||
@Route(path = PushUIConstants.PATH)
|
||||
public class PushModuleProvider implements IMogoModuleProvider {
|
||||
@Override
|
||||
public Fragment createFragment(Context context, Bundle data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoModuleLifecycle getCardLifecycle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoMapListener getMapListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoNaviListener getNaviListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoLocationListener getLocationListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoMarkerClickListener getMarkerClickListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppPackage() {
|
||||
return " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppName() {
|
||||
return " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(final Context context) {
|
||||
}
|
||||
}
|
||||
1
modules/mogo-module-push/.gitignore
vendored
Normal file
1
modules/mogo-module-push/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
12
modules/mogo-module-push/README.md
Normal file
12
modules/mogo-module-push/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 基于 socketserver 实现的push推送
|
||||
|
||||
---
|
||||
目前,仅 launcher 实现推送,独立 app 不用
|
||||
|
||||
## launcher 在前台
|
||||
|
||||
通过launcher内部空白区域的弹层承载推送内容
|
||||
|
||||
## launcher 在后台
|
||||
|
||||
通过 windowmanger 方式承载推送内容
|
||||
71
modules/mogo-module-push/build.gradle
Normal file
71
modules/mogo-module-push/build.gradle
Normal file
@@ -0,0 +1,71 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode Integer.valueOf(VERSION_CODE)
|
||||
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
kapt {
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// 小智语音,免唤醒词等服务
|
||||
compileOnly rootProject.ext.dependencies.mogomap
|
||||
compileOnly rootProject.ext.dependencies.mogoutils
|
||||
compileOnly rootProject.ext.dependencies.mogocommons
|
||||
compileOnly rootProject.ext.dependencies.mogoserviceapi
|
||||
compileOnly rootProject.ext.dependencies.modulecommon
|
||||
compileOnly rootProject.ext.dependencies.androidxconstraintlayout
|
||||
compileOnly rootProject.ext.dependencies.arouter
|
||||
compileOnly rootProject.ext.dependencies.aiassist
|
||||
kapt rootProject.ext.dependencies.aroutercompiler
|
||||
compileOnly rootProject.ext.dependencies.androidxrecyclerview
|
||||
implementation rootProject.ext.dependencies.androidxappcompat
|
||||
implementation rootProject.ext.dependencies.kotlinstdlibjdk7
|
||||
implementation rootProject.ext.dependencies.androidxccorektx
|
||||
implementation rootProject.ext.dependencies.litezxing
|
||||
implementation rootProject.ext.dependencies.androidxroomruntime
|
||||
implementation rootProject.ext.dependencies.androidxroomktx
|
||||
kapt rootProject.ext.dependencies.androidxroomcompiler
|
||||
|
||||
if( Boolean.valueOf(RELEASE) ){
|
||||
implementation rootProject.ext.dependencies.modulepushbase
|
||||
} else {
|
||||
implementation project(":modules:mogo-module-push-base")
|
||||
}
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
0
modules/mogo-module-push/consumer-rules.pro
Normal file
0
modules/mogo-module-push/consumer-rules.pro
Normal file
3
modules/mogo-module-push/gradle.properties
Normal file
3
modules/mogo-module-push/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
GROUP=com.mogo.module
|
||||
POM_ARTIFACT_ID=module-push
|
||||
VERSION_CODE=1
|
||||
21
modules/mogo-module-push/proguard-rules.pro
vendored
Normal file
21
modules/mogo-module-push/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
17
modules/mogo-module-push/src/main/AndroidManifest.xml
Normal file
17
modules/mogo-module-push/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.module.push">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".activity.PushMessageActivity"
|
||||
android:theme="@style/ModulePushMessageTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.mogo.module.push
|
||||
|
||||
object Config {
|
||||
const val PUSH_TYPE = 100 //注册长连接类型
|
||||
const val NEWS_ARRIVE = "news_arrive" //Push到达
|
||||
const val NEWS_CARD_SHOW = "news_card_show"//push 展示
|
||||
const val NEWS_CARD_DISAPPEAR = "news_card_disappear"// push 展示到期,自动消失
|
||||
const val NEWS_CARD_CLICK = "news_card_click"//点击消息体
|
||||
const val NEWS_CARD_SWIPE = "news_card_swipe"//划掉消息
|
||||
const val NEWS_CARD_CLICK_BTN = "news_card_click_btn"//点击按钮
|
||||
|
||||
|
||||
const val NEWS_HISTORY_OPEN = "news_history_open"//打开消息列表
|
||||
const val NEWS_HISTORY_CLOSE = "news_history_close"//关闭消息列表
|
||||
const val NEWS_HISTORY_ALL_CLEAR = "news_history_all_clear"//清除消息列表
|
||||
const val NEWS_HISTORY_ONE_CLEAR = "news_history_one_clear"//清除消息历史中的消息
|
||||
const val NEWS_HISTORY_ONE_CLICK = "news_history_one_click"//点击消息历史中的消息
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.mogo.module.push;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.alibaba.android.arouter.facade.annotation.Route;
|
||||
import com.mogo.map.listener.IMogoMapListener;
|
||||
import com.mogo.map.location.IMogoLocationListener;
|
||||
import com.mogo.map.marker.IMogoMarkerClickListener;
|
||||
import com.mogo.map.navi.IMogoNaviListener;
|
||||
import com.mogo.module.push.base.PushUIConstants;
|
||||
import com.mogo.module.push.repository.PushRepository;
|
||||
import com.mogo.module.push.utils.HandlerUtils;
|
||||
import com.mogo.service.module.IMogoModuleLifecycle;
|
||||
import com.mogo.service.module.IMogoModuleProvider;
|
||||
|
||||
|
||||
@Route(path = PushUIConstants.PATH)
|
||||
public class PushModuleProvider implements IMogoModuleProvider {
|
||||
@Override
|
||||
public Fragment createFragment(Context context, Bundle data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoModuleLifecycle getCardLifecycle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoMapListener getMapListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoNaviListener getNaviListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoLocationListener getLocationListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMogoMarkerClickListener getMarkerClickListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppPackage() {
|
||||
return " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppName() {
|
||||
return " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(final Context context) {
|
||||
HandlerUtils.INSTANCE.getMBgHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PushRepository.Companion.init(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.mogo.module.push.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alibaba.android.arouter.facade.annotation.Route
|
||||
import com.mogo.module.push.Config
|
||||
import com.mogo.module.push.base.PushUIConstants
|
||||
import com.mogo.module.push.R
|
||||
import com.mogo.module.push.adapter.PushMessageAdapter
|
||||
import com.mogo.module.push.model.PushBean
|
||||
import com.mogo.module.push.repository.PushRepository
|
||||
import com.mogo.module.push.utils.AnalyticsUtils
|
||||
import com.mogo.module.push.utils.HandlerUtils
|
||||
import com.mogo.module.push.utils.startClearAnimator
|
||||
import com.mogo.module.push.view.PushItemAnimator
|
||||
import com.mogo.module.push.view.SwipeItemLayout
|
||||
import com.mogo.module.push.viewmodel.MessageViewModel
|
||||
import com.mogo.utils.UiThreadHandler
|
||||
import kotlinx.android.synthetic.main.module_push_message_activity.*
|
||||
|
||||
@Route(path = PushUIConstants.Push_MESSAGE_ACTIVITY_PATH)
|
||||
class PushMessageActivity : AppCompatActivity() {
|
||||
private lateinit var viewModel: MessageViewModel
|
||||
private var adapter = PushMessageAdapter()
|
||||
private var clearing = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
setContentView(R.layout.module_push_message_activity)
|
||||
AnalyticsUtils.track(Config.NEWS_HISTORY_OPEN)
|
||||
module_push_activity_close.setOnClickListener {
|
||||
AnalyticsUtils.track(Config.NEWS_HISTORY_CLOSE)
|
||||
finish()
|
||||
}
|
||||
module_push_activity_clear.setOnClickListener {
|
||||
AnalyticsUtils.track(Config.NEWS_HISTORY_ALL_CLEAR)
|
||||
if (!clearing) {
|
||||
clearing = true
|
||||
startClearAnimator(module_push_activity_recycler_view) {
|
||||
viewModel.deleteAll()
|
||||
clearing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.deletePushBean = object : PushMessageAdapter.PushAdapterListener {
|
||||
override fun lastItemShow(show: Boolean) {
|
||||
if (!show && !clearing) {
|
||||
val size = viewModel.list?.size ?: 0
|
||||
if (size > 0 && size < module_push_activity_recycler_view.childCount) {
|
||||
return
|
||||
}
|
||||
}
|
||||
module_push_activity_clear.visibility =
|
||||
if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun deleteBean(bean: PushBean, action: Boolean) {
|
||||
if (clearing) return
|
||||
viewModel.delete(bean)
|
||||
if (action) {
|
||||
AnalyticsUtils.track(Config.NEWS_HISTORY_ONE_CLICK, "title", bean.title)
|
||||
finish()
|
||||
} else {
|
||||
AnalyticsUtils.track(Config.NEWS_HISTORY_ONE_CLEAR, "title", bean.title)
|
||||
adapter.removeItem(bean)
|
||||
if (adapter.datas?.size ?: 0 == 0) {
|
||||
module_push_activity_not_data.visibility = View.VISIBLE
|
||||
}
|
||||
updateHistoryMessageCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
module_push_activity_recycler_view.layoutManager = LinearLayoutManager(this)
|
||||
module_push_activity_recycler_view.adapter = adapter
|
||||
module_push_activity_recycler_view.itemAnimator = PushItemAnimator()
|
||||
module_push_activity_recycler_view.addOnItemTouchListener(
|
||||
SwipeItemLayout.OnSwipeItemTouchListener(this)
|
||||
)
|
||||
viewModel = MessageViewModel(object :
|
||||
MessageViewModel.MessageListChange {
|
||||
override fun messageListChange(list: MutableList<PushBean>?) {
|
||||
runOnUiThread {
|
||||
var size = list?.size ?: 0
|
||||
adapter.datas = list
|
||||
module_push_activity_not_data.visibility = if (size > 0) View.GONE else View.VISIBLE
|
||||
updateHistoryMessageCount()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateHistoryMessageCount(){
|
||||
HandlerUtils.mBgHandler.post{
|
||||
var count = PushRepository.pushRepository.pushBeanDao.getAllCount()
|
||||
UiThreadHandler.post {
|
||||
module_push_activity_title.text = if (count > 0) "历史消息(${count})" else "历史消息"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.mogo.module.push.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mogo.module.push.R
|
||||
import com.mogo.module.push.model.PushBean
|
||||
import com.mogo.module.push.repository.PushRepository
|
||||
import com.mogo.module.push.utils.dealSchema
|
||||
import com.mogo.module.push.utils.stringConverterBitmap
|
||||
import com.mogo.module.push.view.getApis
|
||||
import com.mogo.service.imageloader.MogoImageView
|
||||
import kotlin.math.abs
|
||||
|
||||
class PushMessageAdapter : RecyclerView.Adapter<PushMessageAdapter.MessageViewHolder>() {
|
||||
interface PushAdapterListener {
|
||||
fun deleteBean(bean: PushBean, action: Boolean)
|
||||
|
||||
fun lastItemShow(show: Boolean)
|
||||
}
|
||||
|
||||
var onAttachStateChangeListener = object : View.OnAttachStateChangeListener {
|
||||
override fun onViewDetachedFromWindow(p0: View?) {
|
||||
deletePushBean.lastItemShow(false)
|
||||
}
|
||||
|
||||
override fun onViewAttachedToWindow(p0: View?) {
|
||||
deletePushBean.lastItemShow(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lateinit var deletePushBean: PushAdapterListener
|
||||
|
||||
var datas: MutableList<PushBean>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun removeItem(bean: PushBean) {
|
||||
datas?.let {
|
||||
val position = it.indexOf(bean)
|
||||
if (position >= 0) {
|
||||
it.removeAt(position)
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
if (itemCount == 0) {
|
||||
deletePushBean.lastItemShow(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
|
||||
return MessageViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.module_push_message_item,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return datas?.size ?: 0
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
|
||||
holder.setPushBean(datas!![position], position)
|
||||
}
|
||||
|
||||
|
||||
inner class MessageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val pushAppIcon: MogoImageView = view.findViewById(R.id.module_push_item_app_icon)
|
||||
private val pushTitle: TextView = view.findViewById(R.id.module_push_item_title)
|
||||
private val pushContent: TextView = view.findViewById(R.id.module_push_item_content)
|
||||
private val pushImage: MogoImageView = view.findViewById(R.id.module_push_item_image)
|
||||
private val pushTimer: TextView = view.findViewById(R.id.module_push_item_time)
|
||||
private val pushDelete: TextView = view.findViewById(R.id.module_push_item_delete)
|
||||
private val pushClick: View = view.findViewById(R.id.module_push_item_click)
|
||||
|
||||
fun setPushBean(bean: PushBean, position: Int) {
|
||||
if (position == (datas?.size ?: 0) - 1) {
|
||||
itemView.addOnAttachStateChangeListener(onAttachStateChangeListener)
|
||||
} else {
|
||||
itemView.removeOnAttachStateChangeListener(onAttachStateChangeListener)
|
||||
}
|
||||
pushDelete.setOnClickListener {
|
||||
deletePushBean.deleteBean(bean, false)
|
||||
}
|
||||
if (!bean.mainSchema.isNullOrEmpty()) {
|
||||
pushClick.setOnClickListener {
|
||||
dealSchema(bean.mainSchema, itemView.context)
|
||||
deletePushBean.deleteBean(bean, true)
|
||||
}
|
||||
} else {
|
||||
pushClick.setOnClickListener(null)
|
||||
}
|
||||
getApis(itemView.context).imageLoaderApi.displayImage(bean.appIcon, pushAppIcon)
|
||||
pushTitle.text = bean.title
|
||||
pushContent.text = bean.content
|
||||
pushContent.visibility = if (bean.content.isNullOrEmpty()) View.GONE else View.VISIBLE
|
||||
if (bean.QRCode.isNullOrEmpty() && bean.imageUrl.isNotEmpty()) {
|
||||
getApis(itemView.context).imageLoaderApi.displayImage(bean.imageUrl, pushImage)
|
||||
}
|
||||
if (!bean.QRCode.isNullOrEmpty()) {
|
||||
pushImage.setImageBitmap(
|
||||
stringConverterBitmap(
|
||||
bean.QRCode,
|
||||
pushImage.context.resources.getDimensionPixelSize(R.dimen.module_push_message_item_image_size),
|
||||
pushImage.context.resources.getDimensionPixelSize(R.dimen.module_push_message_item_image_size)
|
||||
)
|
||||
)
|
||||
}
|
||||
val diff = abs((System.currentTimeMillis() - bean.timestamp) / 1000).toInt()
|
||||
pushTimer.text = when {
|
||||
diff == 0 -> "现在"
|
||||
diff < 60 -> "${diff}秒前"
|
||||
diff < 60 * 60 -> "${diff / 60}分钟前"
|
||||
diff < 60 * 60 * 24 -> "${diff / 60 / 60}小时前"
|
||||
else -> "${diff / 60 / 60 / 24}天前"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.mogo.module.push.dao
|
||||
|
||||
import androidx.room.*
|
||||
import com.mogo.module.push.model.PushBean
|
||||
|
||||
@Dao
|
||||
interface PushBeanDao {
|
||||
@Query("SELECT * FROM pushBean ORDER BY timestamp DESC")
|
||||
fun getAll(): MutableList<PushBean>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAll(vararg bean: PushBean)
|
||||
|
||||
@Delete
|
||||
fun delete(vararg bean: PushBean)
|
||||
|
||||
@Query("DELETE FROM pushBean")
|
||||
fun deleteAll()
|
||||
|
||||
@Query("SELECT count(1) FROM pushBean")
|
||||
fun getAllCount(): Int
|
||||
|
||||
@Query("DELETE FROM pushBean WHERE timestamp IN (SELECT MIN(timestamp) FROM pushBean)")
|
||||
fun deleteMin()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.mogo.module.push.dao
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.mogo.module.push.model.PushBean
|
||||
|
||||
@Database(entities = [PushBean::class], version = 1)
|
||||
abstract class PushBeanDatabase : RoomDatabase() {
|
||||
abstract fun pushBeanDao(): PushBeanDao
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.mogo.module.push.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Entity
|
||||
data class PushBean(
|
||||
@Ignore
|
||||
val speedLimit: Int = 0, //超过速度后延迟显示
|
||||
@Ignore
|
||||
var showTimeout: Int = 0, //显示等待时长
|
||||
@Ignore
|
||||
var showTimeoutShadow: Int = 0, // 显示等待时长备份
|
||||
@ColumnInfo(name = "icon")
|
||||
var appIcon: String = "", //目标app icon图标地址
|
||||
@ColumnInfo(name = "title")
|
||||
var title: String = "", //标题
|
||||
@ColumnInfo(name = "content")
|
||||
var content: String = "", //详细内容
|
||||
@ColumnInfo(name = "image")
|
||||
var imageUrl: String = "", //图片地址
|
||||
@ColumnInfo(name = "qr")
|
||||
var QRCode: String = "", //二维码地址
|
||||
@Ignore
|
||||
val tts: String = "", //语音播报词
|
||||
@ColumnInfo(name = "scheme")
|
||||
var mainSchema: String = "", //schema跳转协议
|
||||
@Ignore
|
||||
val mainVoiceCmd: List<String>? = null, //触发主schema 命令词
|
||||
@Ignore
|
||||
val cancelVoiceCmd: List<String>? = null, //隐藏当前push命令词
|
||||
@Ignore
|
||||
val buttons: List<Button>? = null, //底部buttons列表
|
||||
@PrimaryKey
|
||||
var timestamp: Long = System.currentTimeMillis()
|
||||
|
||||
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is PushBean) {
|
||||
return timestamp == other.timestamp
|
||||
}
|
||||
return super.equals(other)
|
||||
}
|
||||
}
|
||||
|
||||
data class Button(
|
||||
val text: String = "", // button名称
|
||||
val action: String = "", //scheme 协议
|
||||
val voiceCmd: List<String>? = null //注册命令词
|
||||
)
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.mogo.module.push.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.room.Room
|
||||
import com.mogo.module.push.Config
|
||||
import com.mogo.module.push.base.PushUIConstants
|
||||
import com.mogo.module.push.dao.PushBeanDatabase
|
||||
import com.mogo.module.push.model.PushBean
|
||||
import com.mogo.module.push.utils.AnalyticsUtils
|
||||
import com.mogo.module.push.utils.HandlerUtils
|
||||
import com.mogo.module.push.view.getApis
|
||||
import com.mogo.module.push.viewmodel.PushViewModel
|
||||
import com.mogo.service.connection.IMogoOnMessageListener
|
||||
import com.mogo.service.statusmanager.IMogoStatusChangedListener
|
||||
import com.mogo.service.statusmanager.StatusDescriptor
|
||||
import com.mogo.utils.logger.Logger
|
||||
import java.util.*
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class PushRepository(mContext: Context) {
|
||||
|
||||
companion object {
|
||||
private lateinit var appContext: Context
|
||||
fun init(context: Context) {
|
||||
appContext = context.applicationContext
|
||||
pushRepository
|
||||
}
|
||||
|
||||
val globalContext: Context by lazy {
|
||||
appContext
|
||||
}
|
||||
|
||||
val pushRepository: PushRepository by lazy {
|
||||
PushRepository(appContext)
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val TAG: String = "PushRepository.kt"
|
||||
}
|
||||
|
||||
// 被中断的push消息仅再次展示一次
|
||||
private val singleRePushSet = HashSet<PushBean>()
|
||||
|
||||
private val pushViewModel: PushViewModel = PushViewModel(mContext, this)
|
||||
private val pushBeanQueue: Queue<PushBean> = LinkedList()
|
||||
private val statusManager = getApis(mContext).statusManagerApi
|
||||
private val msgCenter = getApis(mContext).msgCenterApi
|
||||
val pushBeanDao by lazy {
|
||||
Room.databaseBuilder(appContext, PushBeanDatabase::class.java, "database_push").build()
|
||||
.pushBeanDao()
|
||||
}
|
||||
private val locationClient =
|
||||
getApis(mContext).mapServiceApi.getSingletonLocationClient(appContext)
|
||||
|
||||
private val mHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
private val statusChangedListener: IMogoStatusChangedListener =
|
||||
IMogoStatusChangedListener { sd, open ->
|
||||
Log.d("PushRepository", "sd = $sd open = $open")
|
||||
}
|
||||
|
||||
init {
|
||||
// 注册push通道监听 type 100时为push消息
|
||||
getApis(mContext).getSocketManagerApi(mContext)
|
||||
.registerOnMessageListener(Config.PUSH_TYPE, object : IMogoOnMessageListener<PushBean> {
|
||||
override fun target(): Class<PushBean> {
|
||||
return PushBean::class.java
|
||||
}
|
||||
|
||||
override fun onMsgReceived(bean: PushBean?) {
|
||||
Log.d("PushRepository", "pushBean = $bean")
|
||||
if (bean != null) {
|
||||
AnalyticsUtils.track(Config.NEWS_ARRIVE, "title", bean.title)
|
||||
if (bean.mainSchema == null || bean.mainSchema == "") {
|
||||
bean.mainSchema = ""
|
||||
}
|
||||
if (bean.imageUrl == null || bean.imageUrl == "null") {
|
||||
bean.imageUrl = ""
|
||||
}
|
||||
if (bean.appIcon == null || bean.appIcon == "null") {
|
||||
bean.appIcon = ""
|
||||
}
|
||||
pushBeanQueue.offer(bean)
|
||||
}
|
||||
startIterate()
|
||||
}
|
||||
})
|
||||
HandlerUtils.mBgHandler.postDelayed({
|
||||
updateMsgNum()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
private fun updateMsgNum() {
|
||||
val num = pushBeanDao.getAllCount()
|
||||
if (num > 100) { //历史消息最多保留100条
|
||||
pushBeanDao.deleteMin()
|
||||
updateMsgNum()
|
||||
return
|
||||
}
|
||||
mHandler.post {
|
||||
msgCenter.setMsgStatus(num > 0, num)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startIterate() {
|
||||
Log.d("PushRepository", "startIterate ${pushViewModel.pushBean}")
|
||||
if (!pushViewModel.isAddWindow()) {
|
||||
val bean = pushBeanQueue.peek()
|
||||
if (bean != null) {
|
||||
if (needDelay(bean)) {
|
||||
mHandler.removeCallbacks(delayRunnable)
|
||||
mHandler.postDelayed(delayRunnable, 15000)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
iterateNext()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存被中断的push消息
|
||||
*/
|
||||
fun push(pushBean: PushBean) {
|
||||
if (singleRePushSet.contains(pushBean)) {
|
||||
return
|
||||
}
|
||||
Logger.d(TAG, "保存待下一次开启")
|
||||
singleRePushSet.add(pushBean)
|
||||
pushBeanQueue.offer(pushBean)
|
||||
}
|
||||
|
||||
private var delayRunnable = {
|
||||
startIterate()
|
||||
}
|
||||
|
||||
private inline fun needDelay(bean: PushBean): Boolean {
|
||||
if (bean == null) {
|
||||
return false
|
||||
}
|
||||
if (locationClient.lastKnowLocation != null) {
|
||||
if (bean.speedLimit > 0 && bean.speedLimit <= locationClient.lastKnowLocation.speed * 18 / 5) {
|
||||
Log.d("PushRepository", "speedLimit" + locationClient.lastKnowLocation.speed)
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (statusManager.isV2XShow) {
|
||||
return true
|
||||
}
|
||||
if (statusManager.isVoiceShow) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun iterateNext(needSave: Boolean = false) {
|
||||
if (needSave && pushViewModel.pushBean != null) {
|
||||
val bean = pushViewModel.pushBean!!
|
||||
HandlerUtils.mBgHandler.post {
|
||||
pushBeanDao.insertAll(bean)
|
||||
updateMsgNum()
|
||||
}
|
||||
}
|
||||
try {
|
||||
val nextBean = pushBeanQueue.peek()
|
||||
if (nextBean == null) {
|
||||
pushViewModel.pushBean = nextBean
|
||||
return
|
||||
}
|
||||
if (needDelay(nextBean)) {
|
||||
mHandler.removeCallbacksAndMessages(null)
|
||||
mHandler.postDelayed({
|
||||
iterateNext(needSave)
|
||||
}, 15000)
|
||||
return
|
||||
}
|
||||
pushViewModel.pushBean = pushBeanQueue.poll()
|
||||
} catch (e: Exception) {
|
||||
Logger.e(TAG, e, "")
|
||||
}
|
||||
if (pushViewModel.pushBean != null) {
|
||||
statusManager.registerStatusChangedListener(
|
||||
PushUIConstants.PATH,
|
||||
StatusDescriptor.V2X_UI,
|
||||
statusChangedListener
|
||||
)
|
||||
statusManager.registerStatusChangedListener(
|
||||
PushUIConstants.PATH,
|
||||
StatusDescriptor.VOICE_UI,
|
||||
statusChangedListener
|
||||
)
|
||||
} else {
|
||||
statusManager.unregisterStatusChangedListener(
|
||||
PushUIConstants.PATH,
|
||||
StatusDescriptor.VOICE_UI,
|
||||
statusChangedListener
|
||||
)
|
||||
statusManager.unregisterStatusChangedListener(
|
||||
PushUIConstants.PATH,
|
||||
StatusDescriptor.V2X_UI,
|
||||
statusChangedListener
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPushUIShow(show: Boolean) {
|
||||
statusManager.setPushUIShow(PushUIConstants.PATH, show)
|
||||
}
|
||||
|
||||
fun delete(bean: PushBean) {
|
||||
HandlerUtils.mBgHandler.post {
|
||||
pushBeanDao.delete(bean)
|
||||
updateMsgNum()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
HandlerUtils.mBgHandler.post {
|
||||
pushBeanDao.deleteAll()
|
||||
mHandler.post {
|
||||
msgCenter.setMsgStatus(false, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAll(): MutableList<PushBean> {
|
||||
return pushBeanDao.getAll()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.mogo.module.push.utils
|
||||
|
||||
import android.util.ArrayMap
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.mogo.module.push.repository.PushRepository
|
||||
import com.mogo.service.MogoServicePaths
|
||||
import com.mogo.service.analytics.IMogoAnalytics
|
||||
|
||||
object AnalyticsUtils {
|
||||
private val Analytics =
|
||||
ARouter.getInstance().build(MogoServicePaths.PATH_UTILS_ANALYTICS).navigation(PushRepository.globalContext) as IMogoAnalytics
|
||||
|
||||
fun track(event: String, vararg keyValue: String) {
|
||||
val map = ArrayMap<String, Any>()
|
||||
if (keyValue.isNotEmpty()) {
|
||||
for (i in 0..keyValue.size - 2 step 2) {
|
||||
map[keyValue[i]] = keyValue[i + 1]
|
||||
}
|
||||
}
|
||||
Analytics.track(event, map)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.mogo.module.push.utils
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isNotEmpty
|
||||
|
||||
fun startClearAnimator(root: ViewGroup, method: () -> Unit) {
|
||||
if (root.isNotEmpty()) {
|
||||
var view: View
|
||||
var size = root.childCount - 1
|
||||
for (i in size downTo 0) {
|
||||
view = root[i]
|
||||
view.animate().translationX(-view.width.toFloat()).apply {
|
||||
if (i == 0) {
|
||||
withEndAction(method)
|
||||
}
|
||||
duration = 200
|
||||
startDelay = 100 * (size - i).toLong()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
method()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.mogo.module.push.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
|
||||
object HandlerUtils {
|
||||
private val handlerThread = HandlerThread("push_ui_thread")
|
||||
val mBgHandler: Handler
|
||||
|
||||
init {
|
||||
handlerThread.start()
|
||||
mBgHandler = Handler(handlerThread.looper)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.mogo.module.push.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
|
||||
private const val ACTION = "AUTONAVI_STANDARD_BROADCAST_RECV"
|
||||
private const val KEY_TYPE = "KEY_TYPE"
|
||||
private const val KEYWORDS = "KEYWORDS"
|
||||
private const val SOURCE_APP = "SOURCE_APP"
|
||||
private const val VALUE_TYPE = "10036"
|
||||
private const val MAP_SCHEME = "map"
|
||||
private const val WELFARE_SCHEME = "welfare"
|
||||
private const val BROADCAST = "broadcast"
|
||||
private const val PACKAGE = "package_name"
|
||||
|
||||
|
||||
fun dealSchema(content: String, context: Context): Boolean {
|
||||
if (!content.isNullOrEmpty()) {
|
||||
val uri = Uri.parse(content)
|
||||
if (uri != null && !uri.scheme.isNullOrEmpty()) {
|
||||
if (mapIsValid(content) && startMap(
|
||||
context,
|
||||
uri
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (checkBroadcastNeedSend(context, uri)) {
|
||||
return true
|
||||
}
|
||||
if (schemeIsValid(context, uri)) {
|
||||
return startActivityForUri(context, uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkBroadcastNeedSend(context: Context, uri: Uri): Boolean {
|
||||
if (BROADCAST == uri.scheme) {
|
||||
if (!uri.host.isNullOrEmpty()) {
|
||||
val intent = Intent(uri.host)
|
||||
intent.putExtra("uri", uri.toString())
|
||||
val packageName = uri.getQueryParameter(PACKAGE)
|
||||
val set = uri.queryParameterNames
|
||||
if (!set.isNullOrEmpty()) {
|
||||
for (key in set) {
|
||||
if(key.isNotEmpty()) {
|
||||
intent.putExtra(key, uri.getQueryParameter(key) ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!packageName.isNullOrEmpty()) {
|
||||
intent.setPackage(packageName)
|
||||
}
|
||||
context.sendBroadcast(intent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun startActivityForUri(context: Context, uri: Uri): Boolean {
|
||||
return try {
|
||||
val intent = Intent()
|
||||
intent.action = "android.intent.action.VIEW"
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.data = uri
|
||||
context.startActivity(intent)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapIsValid(uri: String): Boolean {
|
||||
if (!TextUtils.isEmpty(uri)) {
|
||||
val uriTmp = Uri.parse(uri)
|
||||
return MAP_SCHEME == uriTmp.scheme && VALUE_TYPE.equals(
|
||||
uriTmp.getQueryParameter(KEY_TYPE.toLowerCase())!!,
|
||||
ignoreCase = true
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun welfareIsValid(uri: String): Boolean {
|
||||
if (!TextUtils.isEmpty(uri)) {
|
||||
val uriTmp = Uri.parse(uri)
|
||||
return WELFARE_SCHEME == uriTmp.scheme
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun startMap(context: Context, uri: Uri): Boolean {
|
||||
val intent = Intent(ACTION)
|
||||
return try {
|
||||
intent.putExtra(
|
||||
KEY_TYPE, Integer.valueOf(
|
||||
uri.getQueryParameter(
|
||||
KEY_TYPE.toLowerCase()
|
||||
)!!
|
||||
)
|
||||
)
|
||||
intent.putExtra(KEYWORDS, uri.getQueryParameter(KEYWORDS.toLowerCase()))
|
||||
intent.putExtra(SOURCE_APP, uri.getQueryParameter(SOURCE_APP.toLowerCase()))
|
||||
context.sendBroadcast(intent)
|
||||
true
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e("CustomMessageInfo", e.localizedMessage)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun schemeIsValid(context: Context, uri: Uri): Boolean {
|
||||
val packageManager = context.packageManager
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
val activities = packageManager.queryIntentActivities(intent, 0)
|
||||
var isValid = false
|
||||
try {
|
||||
isValid = activities.isNotEmpty()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.mogo.module.push.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.google.zxing.EncodeHintType
|
||||
import java.util.*
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
|
||||
|
||||
fun stringConverterBitmap(address: String, width: Int, height: Int): Bitmap? {
|
||||
val hints = Hashtable<EncodeHintType, String>()
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val bitMatrix = QRCodeWriter().encode(
|
||||
address,
|
||||
BarcodeFormat.QR_CODE, width, height, hints
|
||||
)
|
||||
val pixels = IntArray(width * height)
|
||||
//下面这里按照二维码的算法,逐个生成二维码的图片,
|
||||
//两个for循环是图片横列扫描的结果
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
if (bitMatrix.get(x, y)) {
|
||||
pixels[y * width + x] = -0x1000000
|
||||
} else {
|
||||
pixels[y * width + x] = -0x1
|
||||
}
|
||||
}
|
||||
}
|
||||
//生成二维码图片的格式,使用ARGB_8888
|
||||
var bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return bitmap
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
package com.mogo.module.push.view
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.PixelFormat
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.*
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.Scroller
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.view.isVisible
|
||||
import com.elegant.analytics.utils.Logger
|
||||
import com.mogo.commons.voice.AIAssist
|
||||
import com.mogo.module.push.Config
|
||||
import com.mogo.module.push.R
|
||||
import com.mogo.module.push.model.PushBean
|
||||
import com.mogo.module.push.utils.AnalyticsUtils
|
||||
import com.mogo.module.push.utils.stringConverterBitmap
|
||||
import com.mogo.module.push.view.roundimage.RoundedImageView
|
||||
import com.mogo.module.push.viewmodel.PushViewModel
|
||||
import com.mogo.service.windowview.IMogoTopViewManager
|
||||
import com.mogo.service.windowview.IMogoTopViewStatusListener
|
||||
import com.mogo.utils.ResourcesHelper
|
||||
import com.mogo.utils.ThreadPoolService
|
||||
import com.mogo.utils.UiThreadHandler
|
||||
import com.mogo.utils.glide.GlideApp
|
||||
|
||||
class FloatView constructor(
|
||||
private val pushViewModel: PushViewModel,
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val TYPE_TOP_VIEW = 1
|
||||
const val TYPE_WINDOW_MANAGER = 2
|
||||
const val TAG: String = "FloatView.kt"
|
||||
}
|
||||
|
||||
interface PushViewController {
|
||||
fun show(bean: PushBean?)
|
||||
fun hide()
|
||||
fun timer(time: Int)
|
||||
fun inflateView(@LayoutRes layoutId: Int)
|
||||
}
|
||||
|
||||
open abstract inner class PushView(context: Context) : FrameLayout(context),
|
||||
PushViewController {
|
||||
lateinit var appIcon: ImageView
|
||||
lateinit var titleIconContainer: View
|
||||
lateinit var pushTitle: TextView
|
||||
lateinit var pushImage: RoundedImageView
|
||||
lateinit var pushContent: TextView
|
||||
lateinit var pushTimer: TextView
|
||||
lateinit var pushButtonLeft: TextView
|
||||
lateinit var pushButtonRight: TextView
|
||||
lateinit var pushButton: View
|
||||
|
||||
override fun inflateView(layoutId: Int) {
|
||||
LayoutInflater.from(context).inflate(layoutId, this, true)
|
||||
appIcon = findViewById(R.id.module_push_app_icon)
|
||||
pushTitle = findViewById(R.id.module_push_title)
|
||||
pushImage = findViewById(R.id.module_push_image)
|
||||
pushContent = findViewById(R.id.module_push_content)
|
||||
pushButtonLeft = findViewById(R.id.module_push_button_left)
|
||||
pushButtonRight = findViewById(R.id.module_push_button_right)
|
||||
pushTimer = findViewById(R.id.module_push_timer)
|
||||
titleIconContainer = findViewById(R.id.module_push_app_icon_title)
|
||||
pushButton = findViewById(R.id.module_push_buttons)
|
||||
setOnClickListener {
|
||||
pushViewModel.dealCmd(PushViewModel.VOICE_ACTION_PUSH_MAIN, "1")
|
||||
turnNextMessage()
|
||||
}
|
||||
pushButtonLeft.setOnClickListener {
|
||||
pushViewModel.dealCmd(PushViewModel.VOICE_ACTION_PUSH_LEFT, "1")
|
||||
turnNextMessage()
|
||||
}
|
||||
pushButtonRight.setOnClickListener {
|
||||
pushViewModel.dealCmd(PushViewModel.VOICE_ACTION_PUSH_RIGHT, "1")
|
||||
turnNextMessage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun turnNextMessage() {
|
||||
pushViewModel?.pushBean?.apply {
|
||||
showTimeout = 0
|
||||
updateTimer()
|
||||
}
|
||||
}
|
||||
|
||||
fun hasButtons(bean: PushBean?): Boolean {
|
||||
bean?.buttons?.forEach {
|
||||
if (!it?.text?.isNullOrEmpty()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun hasTextContent(bean: PushBean?): Boolean =
|
||||
bean?.content?.isNullOrEmpty()?.not() ?: false
|
||||
|
||||
fun hasImgContent(bean: PushBean?): Boolean = bean?.QRCode?.isNullOrEmpty()?.not() ?: false
|
||||
|
||||
open fun setBean(bean: PushBean) {
|
||||
// app icon
|
||||
if (!bean.appIcon.isNullOrEmpty()) {
|
||||
appIcon.visible()
|
||||
GlideApp.with(this).load(bean.appIcon).into(appIcon)
|
||||
} else {
|
||||
appIcon.gone()
|
||||
}
|
||||
|
||||
// title
|
||||
pushTitle.text = bean.title
|
||||
|
||||
// decrease timer
|
||||
pushTimer.text = if (bean.showTimeout > 99) "" else "${bean.showTimeout}s"
|
||||
|
||||
// image
|
||||
if (bean.imageUrl.isNullOrEmpty() && bean.QRCode.isNullOrEmpty()) {
|
||||
pushImage.gone()
|
||||
} else if (!bean.imageUrl.isNullOrEmpty()) {
|
||||
var params = pushImage.layoutParams
|
||||
params.width = getImgWidth()
|
||||
params.height = getImgHeight()
|
||||
pushImage.layoutParams = params
|
||||
pushImage.visible()
|
||||
GlideApp.with(this).load(bean.imageUrl).into(pushImage)
|
||||
} else if (!bean.QRCode.isNullOrEmpty()) {
|
||||
var params = pushImage.layoutParams
|
||||
params.width = getQrImgWidth()
|
||||
params.height = getQrImgHeight()
|
||||
pushImage.layoutParams = params
|
||||
ThreadPoolService.execute {
|
||||
val bmp = stringConverterBitmap(
|
||||
bean.QRCode,
|
||||
getQrImgWidth(),
|
||||
getQrImgHeight()
|
||||
)
|
||||
UiThreadHandler.post {
|
||||
pushImage.setImageBitmap(bmp)
|
||||
pushImage.visible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// button
|
||||
pushButton.gone()
|
||||
pushButtonLeft.gone()
|
||||
pushButtonRight.gone()
|
||||
if (!bean.buttons.isNullOrEmpty()) {
|
||||
if (bean.buttons[0].text.isNotEmpty()) {
|
||||
pushButton.visible()
|
||||
pushButtonLeft.text = bean.buttons[0].text
|
||||
pushButtonLeft.visible()
|
||||
}
|
||||
if (bean.buttons.size > 1 && bean.buttons[1].text.isNotEmpty()) {
|
||||
pushButtonRight.text = bean.buttons[1].text
|
||||
pushButtonRight.visible()
|
||||
}
|
||||
}
|
||||
|
||||
// content
|
||||
if (bean.content.isNullOrEmpty()) {
|
||||
pushContent.gone()
|
||||
} else {
|
||||
pushContent.text = bean.content
|
||||
pushContent.visible()
|
||||
}
|
||||
|
||||
// tts
|
||||
if (!bean?.tts?.isNullOrEmpty()) {
|
||||
AIAssist.getInstance(context).speakTTSVoice(bean.tts)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getImgWidth(): Int
|
||||
abstract fun getImgHeight(): Int
|
||||
abstract fun getQrImgWidth(): Int
|
||||
abstract fun getQrImgHeight(): Int
|
||||
|
||||
override fun timer(time: Int) {
|
||||
Logger.d(TAG, "time = $time")
|
||||
pushTimer.text = "${time}s"
|
||||
}
|
||||
|
||||
override fun show(bean: PushBean?) {
|
||||
isAddWindow = true
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
}
|
||||
|
||||
override fun hide() {
|
||||
isAddWindow = false
|
||||
}
|
||||
}
|
||||
|
||||
open inner class PushViewInTopView(context: Context) : PushView(context) {
|
||||
|
||||
private val mTopViewManager: IMogoTopViewManager = getApis(context).topViewManager
|
||||
|
||||
init {
|
||||
inflateView(R.layout.module_push_item)
|
||||
}
|
||||
|
||||
private var topViewStatusListener = object : IMogoTopViewStatusListener {
|
||||
override fun onViewRemoved(view: View?) {
|
||||
isAddWindow = false
|
||||
if (pushViewModel?.pushBean?.showTimeout ?: 0 > 0) {
|
||||
if (getApis(context).statusManagerApi.isV2XShow) {
|
||||
// 被中断的消息,需要再次被显示一次
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
pushViewModel.push()
|
||||
pushViewModel.pushMessageFinish()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewAdded(view: View?) {
|
||||
if (pushViewModel.pushBean != null) {
|
||||
startClosePush()
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeViewRemoveAnim(view: View?) {
|
||||
}
|
||||
|
||||
override fun beforeViewAddAnim(view: View?) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun show(bean: PushBean?) {
|
||||
super.show(bean)
|
||||
mLastVisibleType = TYPE_TOP_VIEW
|
||||
mTopViewManager.addView(this, topViewStatusListener)
|
||||
setBean(bean!!)
|
||||
}
|
||||
|
||||
override fun hide() {
|
||||
super.hide()
|
||||
mTopViewManager.removeView(this)
|
||||
}
|
||||
|
||||
override fun getImgWidth(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_width)
|
||||
|
||||
override fun getImgHeight(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height)
|
||||
|
||||
override fun getQrImgWidth(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height)
|
||||
|
||||
override fun getQrImgHeight(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height)
|
||||
}
|
||||
|
||||
inner class PushViewInWindowView(context: Context) : PushView(context), View.OnTouchListener {
|
||||
|
||||
private val mContentContainer: View
|
||||
private val mWindowManager =
|
||||
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
|
||||
private val params = WindowManager.LayoutParams()
|
||||
|
||||
init {
|
||||
|
||||
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
|
||||
params.flags = (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
|
||||
or WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE
|
||||
or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
|
||||
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
params.gravity = Gravity.LEFT or Gravity.BOTTOM
|
||||
params.format = PixelFormat.TRANSLUCENT
|
||||
params.x = context.resources.getDimensionPixelSize(R.dimen.module_push_window_x)
|
||||
params.y = context.resources.getDimensionPixelSize(R.dimen.module_push_window_x)
|
||||
if (Build.VERSION.SDK_INT > 25) {
|
||||
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||
}
|
||||
|
||||
inflateView(R.layout.module_push_item_vertical)
|
||||
mContentContainer = findViewById(R.id.module_push_content_container)
|
||||
setOnTouchListener(this)
|
||||
}
|
||||
|
||||
override fun show(bean: PushBean?) {
|
||||
super.show(bean)
|
||||
mLastVisibleType = TYPE_WINDOW_MANAGER
|
||||
setBean(bean!!)
|
||||
|
||||
try {
|
||||
mWindowManager.addView(this, params)
|
||||
} catch (e: Exception) {
|
||||
mWindowManager.updateViewLayout(this, params)
|
||||
}
|
||||
translationXAnimation(
|
||||
-ResourcesHelper.getDimension(context, R.dimen.module_push_ui_width_vertical),
|
||||
0f
|
||||
) {
|
||||
if (pushViewModel.pushBean != null) {
|
||||
startClosePush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translationXAnimation(
|
||||
from: Float,
|
||||
to: Float,
|
||||
doOnEnd: (animator: Animator) -> Unit
|
||||
) {
|
||||
val transitionXAnimator: ObjectAnimator =
|
||||
ObjectAnimator.ofFloat(
|
||||
this,
|
||||
View.TRANSLATION_X,
|
||||
from,
|
||||
to
|
||||
)
|
||||
transitionXAnimator.duration = 200
|
||||
transitionXAnimator.doOnEnd(doOnEnd)
|
||||
transitionXAnimator.start()
|
||||
}
|
||||
|
||||
override fun setBean(bean: PushBean) {
|
||||
super.setBean(bean)
|
||||
var paddingBottom: Int = 0
|
||||
if (pushButton.isVisible) {
|
||||
paddingBottom =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_content_paddingBottom_vertical)
|
||||
}
|
||||
mContentContainer.setPadding(0, 0, 0, paddingBottom)
|
||||
}
|
||||
|
||||
override fun hide() {
|
||||
super.hide()
|
||||
translationXAnimation(
|
||||
this.x,
|
||||
-ResourcesHelper.getDimension(context, R.dimen.module_push_ui_width_vertical)
|
||||
) {
|
||||
Logger.d(TAG, "here")
|
||||
this.x = 0f
|
||||
mWindowManager.removeViewImmediate(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouch(v: View?, ev: MotionEvent?): Boolean {
|
||||
|
||||
if (mLastVisibleType != TYPE_WINDOW_MANAGER) {
|
||||
return false
|
||||
}
|
||||
when (ev?.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
needInterceptClick = false
|
||||
startX = ev.x
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
moveX = startX - ev.x
|
||||
scrollBy(moveX.toInt(), 0)
|
||||
startX = ev.x
|
||||
if (scrollX < 0) {
|
||||
scrollTo(0, 0)
|
||||
}
|
||||
if (!needInterceptClick && scrollX > 20) {
|
||||
needInterceptClick = true
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (scrollX > 0) {
|
||||
mScroller.startScroll(scrollX, 0, width - scrollX, 0)
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
if (needInterceptClick) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
startX = 0f
|
||||
moveX = 0f
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun computeScroll() {
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
scrollTo(mScroller.currX, mScroller.currY)
|
||||
invalidate()
|
||||
} else {
|
||||
if (mScroller.currX == 0) {
|
||||
return
|
||||
}
|
||||
mScroller.finalX = 0
|
||||
removeCallbacks(delayClosePush)
|
||||
if (isAddWindow) {
|
||||
if (currentBean != null) {
|
||||
AnalyticsUtils.track(Config.NEWS_CARD_SWIPE, "trigger_type", "1")
|
||||
}
|
||||
mWindowManager.removeView(this)
|
||||
isAddWindow = false
|
||||
}
|
||||
pushViewModel.pushBean?.showTimeout = 0
|
||||
updateTimer()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getImgWidth(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_width_vertical)
|
||||
|
||||
override fun getImgHeight(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_ui_image_height_vertical)
|
||||
|
||||
override fun getQrImgWidth(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_image_qr_size_vertical)
|
||||
|
||||
override fun getQrImgHeight(): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.module_push_image_qr_size_vertical)
|
||||
}
|
||||
|
||||
private val delayClosePush: Runnable
|
||||
private var isAddWindow = false
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
private var startX = 0f
|
||||
private var moveX = 0f
|
||||
private val mScroller: Scroller = Scroller(context)
|
||||
private var needInterceptClick = false
|
||||
private var pause = false
|
||||
|
||||
private var currentBean: PushBean? = null
|
||||
|
||||
|
||||
private var mLastVisibleType = -1
|
||||
private var pushViewController: PushViewController? = null
|
||||
|
||||
init {
|
||||
delayClosePush = Runnable {
|
||||
updateTimer()
|
||||
}
|
||||
}
|
||||
|
||||
fun pushBeanChanged(bean: PushBean?) {
|
||||
uiHandler.post {
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
if (bean == null) {
|
||||
if (currentBean != null) {
|
||||
AnalyticsUtils.track(Config.NEWS_CARD_DISAPPEAR, "title", currentBean!!.title)
|
||||
}
|
||||
hide()
|
||||
} else {
|
||||
show(bean)
|
||||
AnalyticsUtils.track(Config.NEWS_CARD_SHOW, "title", bean.title)
|
||||
}
|
||||
currentBean = bean
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTimer() {
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
var time = pushViewModel.pushBean?.showTimeout ?: 0
|
||||
if (time > 0) {
|
||||
pushViewModel.pushBean!!.showTimeout--
|
||||
pushViewController?.timer(time)
|
||||
uiHandler.postDelayed(delayClosePush, 1000)
|
||||
} else {
|
||||
pushViewController?.timer(0)
|
||||
pushViewModel.pushMessageFinish(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun show(bean: PushBean) {
|
||||
if (isAddWindow) {
|
||||
if (getApis(context).statusManagerApi.isMainPageOnResume) {
|
||||
if (mLastVisibleType != TYPE_TOP_VIEW) {
|
||||
hide()
|
||||
(pushViewController as View).postDelayed({
|
||||
show(bean)
|
||||
}, 750L)
|
||||
} else {
|
||||
showByTopView(bean)
|
||||
}
|
||||
} else {
|
||||
if (mLastVisibleType != TYPE_WINDOW_MANAGER) {
|
||||
hide()
|
||||
(pushViewController as View).postDelayed({
|
||||
show(bean)
|
||||
}, 750L)
|
||||
} else {
|
||||
showByWindowManager(bean)
|
||||
}
|
||||
}
|
||||
startClosePush()
|
||||
} else {
|
||||
if (getApis(context).statusManagerApi.isMainPageOnResume) {
|
||||
showByTopView(bean)
|
||||
} else {
|
||||
showByWindowManager(bean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showByTopView(bean: PushBean) {
|
||||
if (pushViewController !is PushViewInTopView) {
|
||||
pushViewController = PushViewInTopView(context)
|
||||
}
|
||||
pushViewController?.show(bean)
|
||||
}
|
||||
|
||||
private fun showByWindowManager(bean: PushBean?) {
|
||||
if (pushViewController !is PushViewInWindowView) {
|
||||
pushViewController = PushViewInWindowView(context)
|
||||
}
|
||||
pushViewController?.show(bean)
|
||||
}
|
||||
|
||||
private fun startClosePush() {
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
uiHandler.postDelayed(
|
||||
delayClosePush,
|
||||
1000L
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun hide() {
|
||||
if (!isAddWindow) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
pushViewController?.hide()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun pauseTimer(on: Boolean) {
|
||||
if (on) {
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
} else {
|
||||
updateTimer()
|
||||
uiHandler.post {
|
||||
if (on) {
|
||||
pause = true
|
||||
uiHandler.removeCallbacks(delayClosePush)
|
||||
} else if (pause) {
|
||||
pause = false
|
||||
updateTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isAddWindow(): Boolean = isAddWindow
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.mogo.module.push.view
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.mogo.service.IMogoServiceApis
|
||||
import com.mogo.service.MogoServicePaths
|
||||
|
||||
/**
|
||||
* @author congtaowang
|
||||
* @since 2020/6/28
|
||||
*
|
||||
* 描述
|
||||
*/
|
||||
private var apis: IMogoServiceApis? = null
|
||||
|
||||
fun getApis(context: Context): IMogoServiceApis {
|
||||
if (apis == null) {
|
||||
apis = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation(context) as IMogoServiceApis;
|
||||
}
|
||||
return apis!!
|
||||
}
|
||||
|
||||
fun View.gone() {
|
||||
if (this.visibility != View.GONE) {
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun View.visible() {
|
||||
if (this.visibility != View.VISIBLE) {
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
fun View.invisible() {
|
||||
if (this.visibility != View.INVISIBLE) {
|
||||
this.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
package com.mogo.module.push.view;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.View;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
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 PushItemAnimator extends SimpleItemAnimator {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static TimeInterpolator sDefaultInterpolator;
|
||||
|
||||
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
|
||||
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
|
||||
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
|
||||
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
|
||||
|
||||
ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>();
|
||||
ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
|
||||
ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
|
||||
|
||||
ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>();
|
||||
ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>();
|
||||
ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>();
|
||||
ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>();
|
||||
|
||||
private static class MoveInfo {
|
||||
public RecyclerView.ViewHolder holder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
MoveInfo(RecyclerView.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 RecyclerView.ViewHolder oldHolder, newHolder;
|
||||
public int fromX, fromY, toX, toY;
|
||||
|
||||
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
|
||||
this.oldHolder = oldHolder;
|
||||
this.newHolder = newHolder;
|
||||
}
|
||||
|
||||
ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.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 (RecyclerView.ViewHolder holder : mPendingRemovals) {
|
||||
animateRemoveImpl(holder);
|
||||
}
|
||||
mPendingRemovals.clear();
|
||||
// Next, move stuff
|
||||
if (movesPending) {
|
||||
final ArrayList<MoveInfo> moves = new ArrayList<>();
|
||||
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<>();
|
||||
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) {
|
||||
RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
|
||||
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
|
||||
} else {
|
||||
changer.run();
|
||||
}
|
||||
}
|
||||
// Next, add stuff
|
||||
if (additionsPending) {
|
||||
final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
|
||||
additions.addAll(mPendingAdditions);
|
||||
mAdditionsList.add(additions);
|
||||
mPendingAdditions.clear();
|
||||
Runnable adder = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (RecyclerView.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 RecyclerView.ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
mPendingRemovals.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimator animation = view.animate();
|
||||
mRemoveAnimations.add(holder);
|
||||
animation.setDuration(getRemoveDuration()).alpha(0).translationX(-view.getWidth()).setListener(
|
||||
new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchRemoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
animation.setListener(null);
|
||||
view.setAlpha(1);
|
||||
dispatchRemoveFinished(holder);
|
||||
mRemoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
|
||||
resetAnimation(holder);
|
||||
holder.itemView.setAlpha(0);
|
||||
mPendingAdditions.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
void animateAddImpl(final RecyclerView.ViewHolder holder) {
|
||||
final View view = holder.itemView;
|
||||
final ViewPropertyAnimator animation = view.animate();
|
||||
mAddAnimations.add(holder);
|
||||
animation.alpha(1).setDuration(getAddDuration())
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchAddStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
view.setAlpha(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
animation.setListener(null);
|
||||
dispatchAddFinished(holder);
|
||||
mAddAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
|
||||
int toX, int toY) {
|
||||
final View view = holder.itemView;
|
||||
fromX += (int) holder.itemView.getTranslationX();
|
||||
fromY += (int) holder.itemView.getTranslationY();
|
||||
resetAnimation(holder);
|
||||
int deltaX = toX - fromX;
|
||||
int deltaY = toY - fromY;
|
||||
if (deltaX == 0 && deltaY == 0) {
|
||||
dispatchMoveFinished(holder);
|
||||
return false;
|
||||
}
|
||||
if (deltaX != 0) {
|
||||
view.setTranslationX(-deltaX);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
view.setTranslationY(-deltaY);
|
||||
}
|
||||
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
void animateMoveImpl(final RecyclerView.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) {
|
||||
view.animate().translationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
view.animate().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.
|
||||
final ViewPropertyAnimator animation = view.animate();
|
||||
mMoveAnimations.add(holder);
|
||||
animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchMoveStarting(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
if (deltaX != 0) {
|
||||
view.setTranslationX(0);
|
||||
}
|
||||
if (deltaY != 0) {
|
||||
view.setTranslationY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
animation.setListener(null);
|
||||
dispatchMoveFinished(holder);
|
||||
mMoveAnimations.remove(holder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
|
||||
int fromX, int fromY, int toX, int toY) {
|
||||
if (oldHolder == newHolder) {
|
||||
// Don't know how to run change animations when the same view holder is re-used.
|
||||
// run a move animation to handle position changes.
|
||||
return animateMove(oldHolder, fromX, fromY, toX, toY);
|
||||
}
|
||||
final float prevTranslationX = oldHolder.itemView.getTranslationX();
|
||||
final float prevTranslationY = oldHolder.itemView.getTranslationY();
|
||||
final float prevAlpha = oldHolder.itemView.getAlpha();
|
||||
resetAnimation(oldHolder);
|
||||
int deltaX = (int) (toX - fromX - prevTranslationX);
|
||||
int deltaY = (int) (toY - fromY - prevTranslationY);
|
||||
// recover prev translation state after ending animation
|
||||
oldHolder.itemView.setTranslationX(prevTranslationX);
|
||||
oldHolder.itemView.setTranslationY(prevTranslationY);
|
||||
oldHolder.itemView.setAlpha(prevAlpha);
|
||||
if (newHolder != null) {
|
||||
// carry over translation values
|
||||
resetAnimation(newHolder);
|
||||
newHolder.itemView.setTranslationX(-deltaX);
|
||||
newHolder.itemView.setTranslationY(-deltaY);
|
||||
newHolder.itemView.setAlpha(0);
|
||||
}
|
||||
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
|
||||
return true;
|
||||
}
|
||||
|
||||
void animateChangeImpl(final ChangeInfo changeInfo) {
|
||||
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
|
||||
final View view = holder == null ? null : holder.itemView;
|
||||
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
|
||||
final View newView = newHolder != null ? newHolder.itemView : null;
|
||||
if (view != null) {
|
||||
final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
|
||||
getChangeDuration());
|
||||
mChangeAnimations.add(changeInfo.oldHolder);
|
||||
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
|
||||
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
|
||||
oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchChangeStarting(changeInfo.oldHolder, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
oldViewAnim.setListener(null);
|
||||
view.setAlpha(1);
|
||||
view.setTranslationX(0);
|
||||
view.setTranslationY(0);
|
||||
dispatchChangeFinished(changeInfo.oldHolder, true);
|
||||
mChangeAnimations.remove(changeInfo.oldHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
if (newView != null) {
|
||||
final ViewPropertyAnimator newViewAnimation = newView.animate();
|
||||
mChangeAnimations.add(changeInfo.newHolder);
|
||||
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
|
||||
.alpha(1).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
dispatchChangeStarting(changeInfo.newHolder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
newViewAnimation.setListener(null);
|
||||
newView.setAlpha(1);
|
||||
newView.setTranslationX(0);
|
||||
newView.setTranslationY(0);
|
||||
dispatchChangeFinished(changeInfo.newHolder, false);
|
||||
mChangeAnimations.remove(changeInfo.newHolder);
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.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, RecyclerView.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;
|
||||
}
|
||||
item.itemView.setAlpha(1);
|
||||
item.itemView.setTranslationX(0);
|
||||
item.itemView.setTranslationY(0);
|
||||
dispatchChangeFinished(item, oldItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimation(RecyclerView.ViewHolder item) {
|
||||
final View view = item.itemView;
|
||||
// this will trigger end callback which should set properties to their target values.
|
||||
view.animate().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) {
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(item);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
}
|
||||
endChangeAnimation(mPendingChanges, item);
|
||||
if (mPendingRemovals.remove(item)) {
|
||||
view.setAlpha(1);
|
||||
dispatchRemoveFinished(item);
|
||||
}
|
||||
if (mPendingAdditions.remove(item)) {
|
||||
view.setAlpha(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(i);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(item);
|
||||
moves.remove(j);
|
||||
if (moves.isEmpty()) {
|
||||
mMovesList.remove(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
|
||||
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
|
||||
if (additions.remove(item)) {
|
||||
view.setAlpha(1);
|
||||
dispatchAddFinished(item);
|
||||
if (additions.isEmpty()) {
|
||||
mAdditionsList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// animations should be ended by the cancel above.
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mRemoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mRemoveAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mAddAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mAddAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mChangeAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mChangeAnimations list");
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression,ConstantConditions
|
||||
if (mMoveAnimations.remove(item) && DEBUG) {
|
||||
throw new IllegalStateException("after animation is cancelled, item should not be in "
|
||||
+ "mMoveAnimations list");
|
||||
}
|
||||
dispatchFinishedWhenDone();
|
||||
}
|
||||
|
||||
private void resetAnimation(RecyclerView.ViewHolder holder) {
|
||||
if (sDefaultInterpolator == null) {
|
||||
sDefaultInterpolator = new ValueAnimator().getInterpolator();
|
||||
}
|
||||
holder.itemView.animate().setInterpolator(sDefaultInterpolator);
|
||||
endAnimation(holder);
|
||||
}
|
||||
|
||||
@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.
|
||||
*/
|
||||
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;
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(0);
|
||||
dispatchMoveFinished(item.holder);
|
||||
mPendingMoves.remove(i);
|
||||
}
|
||||
count = mPendingRemovals.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
RecyclerView.ViewHolder item = mPendingRemovals.get(i);
|
||||
dispatchRemoveFinished(item);
|
||||
mPendingRemovals.remove(i);
|
||||
}
|
||||
count = mPendingAdditions.size();
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
RecyclerView.ViewHolder item = mPendingAdditions.get(i);
|
||||
item.itemView.setAlpha(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);
|
||||
RecyclerView.ViewHolder item = moveInfo.holder;
|
||||
View view = item.itemView;
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationX(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<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
|
||||
count = additions.size();
|
||||
for (int j = count - 1; j >= 0; j--) {
|
||||
RecyclerView.ViewHolder item = additions.get(j);
|
||||
View view = item.itemView;
|
||||
view.setAlpha(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<RecyclerView.ViewHolder> viewHolders) {
|
||||
for (int i = viewHolders.size() - 1; i >= 0; i--) {
|
||||
viewHolders.get(i).itemView.animate().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
|
||||
* When this is the case:
|
||||
* <ul>
|
||||
* <li>If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both
|
||||
* ViewHolder arguments will be the same instance.
|
||||
* </li>
|
||||
* <li>
|
||||
* If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
|
||||
* then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and
|
||||
* run a move animation instead.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull List<Object> payloads) {
|
||||
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
package com.mogo.module.push.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.Scroller;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* recyclerview滑动删除类
|
||||
*/
|
||||
public class SwipeItemLayout extends ViewGroup {
|
||||
enum Mode {
|
||||
RESET, DRAG, FLING, TAP
|
||||
}
|
||||
|
||||
private Mode mTouchMode;
|
||||
|
||||
private ViewGroup mMainView;
|
||||
private ViewGroup mSideView;
|
||||
|
||||
private ScrollRunnable mScrollRunnable;
|
||||
private int mScrollOffset;
|
||||
private int mMaxScrollOffset;
|
||||
|
||||
private boolean mInLayout;
|
||||
private boolean mIsLaidOut;
|
||||
|
||||
public void setOpenSwipe(boolean mOpenSwipe) {
|
||||
this.mOpenSwipe = mOpenSwipe;
|
||||
}
|
||||
|
||||
private boolean mOpenSwipe = true;
|
||||
|
||||
public SwipeItemLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SwipeItemLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mTouchMode = Mode.RESET;
|
||||
mScrollOffset = 0;
|
||||
mIsLaidOut = false;
|
||||
|
||||
mScrollRunnable = new ScrollRunnable(context);
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return mScrollOffset != 0;
|
||||
}
|
||||
|
||||
Mode getTouchMode() {
|
||||
return mTouchMode;
|
||||
}
|
||||
|
||||
void setTouchMode(Mode mode) {
|
||||
switch (mTouchMode) {
|
||||
case FLING:
|
||||
mScrollRunnable.abort();
|
||||
break;
|
||||
case RESET:
|
||||
break;
|
||||
}
|
||||
|
||||
mTouchMode = mode;
|
||||
}
|
||||
|
||||
public void open() {
|
||||
if (mScrollOffset != -mMaxScrollOffset) {
|
||||
//正在open,不需要处理
|
||||
if (mTouchMode == Mode.FLING && mScrollRunnable.isScrollToLeft())
|
||||
return;
|
||||
|
||||
//当前正在向右滑,abort
|
||||
if (mTouchMode == Mode.FLING /*&& !mScrollRunnable.mScrollToLeft*/)
|
||||
mScrollRunnable.abort();
|
||||
|
||||
mScrollRunnable.startScroll(mScrollOffset, -mMaxScrollOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
|
||||
super.addOnAttachStateChangeListener(listener);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (mScrollOffset != 0) {
|
||||
//正在close,不需要处理
|
||||
if (mTouchMode == Mode.FLING && !mScrollRunnable.isScrollToLeft())
|
||||
return;
|
||||
|
||||
//当前正向左滑,abort
|
||||
if (mTouchMode == Mode.FLING /*&& mScrollRunnable.mScrollToLeft*/)
|
||||
mScrollRunnable.abort();
|
||||
|
||||
mScrollRunnable.startScroll(mScrollOffset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void fling(int xVel) {
|
||||
mScrollRunnable.startFling(mScrollOffset, xVel);
|
||||
}
|
||||
|
||||
void revise() {
|
||||
if (mScrollOffset < -mMaxScrollOffset / 2)
|
||||
open();
|
||||
else
|
||||
close();
|
||||
}
|
||||
|
||||
boolean trackMotionScroll(int deltaX) {
|
||||
if (deltaX == 0)
|
||||
return false;
|
||||
|
||||
boolean over = false;
|
||||
int newLeft = mScrollOffset + deltaX;
|
||||
if ((deltaX > 0 && newLeft > 0) || (deltaX < 0 && newLeft < -mMaxScrollOffset)) {
|
||||
over = true;
|
||||
newLeft = Math.min(newLeft, 0);
|
||||
newLeft = Math.max(newLeft, -mMaxScrollOffset);
|
||||
}
|
||||
|
||||
offsetChildrenLeftAndRight(newLeft - mScrollOffset);
|
||||
mScrollOffset = newLeft;
|
||||
return over;
|
||||
}
|
||||
|
||||
private boolean ensureChildren() {
|
||||
int childCount = getChildCount();
|
||||
|
||||
if (childCount != 2)
|
||||
return false;
|
||||
|
||||
View childView = getChildAt(0);
|
||||
if (!(childView instanceof ViewGroup))
|
||||
return false;
|
||||
mMainView = (ViewGroup) childView;
|
||||
|
||||
childView = getChildAt(1);
|
||||
if (!(childView instanceof ViewGroup))
|
||||
return false;
|
||||
mSideView = (ViewGroup) childView;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (!ensureChildren())
|
||||
throw new RuntimeException("SwipeItemLayout的子视图不符合规定");
|
||||
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
MarginLayoutParams lp = null;
|
||||
int horizontalMargin, verticalMargin;
|
||||
int horizontalPadding = getPaddingLeft() + getPaddingRight();
|
||||
int verticalPadding = getPaddingTop() + getPaddingBottom();
|
||||
|
||||
lp = (MarginLayoutParams) mMainView.getLayoutParams();
|
||||
horizontalMargin = lp.leftMargin + lp.rightMargin;
|
||||
verticalMargin = lp.topMargin + lp.bottomMargin;
|
||||
measureChildWithMargins(mMainView,
|
||||
widthMeasureSpec, horizontalMargin + horizontalPadding,
|
||||
heightMeasureSpec, verticalMargin + verticalPadding);
|
||||
|
||||
if (widthMode == MeasureSpec.AT_MOST)
|
||||
widthSize = Math.min(widthSize, mMainView.getMeasuredWidth() + horizontalMargin + horizontalPadding);
|
||||
else if (widthMode == MeasureSpec.UNSPECIFIED)
|
||||
widthSize = mMainView.getMeasuredWidth() + horizontalMargin + horizontalPadding;
|
||||
|
||||
if (heightMode == MeasureSpec.AT_MOST)
|
||||
heightSize = Math.min(heightSize, mMainView.getMeasuredHeight() + verticalMargin + verticalPadding);
|
||||
else if (heightMode == MeasureSpec.UNSPECIFIED)
|
||||
heightSize = mMainView.getMeasuredHeight() + verticalMargin + verticalPadding;
|
||||
|
||||
setMeasuredDimension(widthSize, heightSize);
|
||||
|
||||
//side layout大小为自身实际大小
|
||||
lp = (MarginLayoutParams) mSideView.getLayoutParams();
|
||||
verticalMargin = lp.topMargin + lp.bottomMargin;
|
||||
mSideView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
|
||||
MeasureSpec.makeMeasureSpec(getMeasuredHeight() - verticalMargin - verticalPadding, MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (!ensureChildren())
|
||||
throw new RuntimeException("SwipeItemLayout的子视图不符合规定");
|
||||
|
||||
mInLayout = true;
|
||||
|
||||
int pl = getPaddingLeft();
|
||||
int pt = getPaddingTop();
|
||||
int pr = getPaddingRight();
|
||||
int pb = getPaddingBottom();
|
||||
|
||||
MarginLayoutParams mainLp = (MarginLayoutParams) mMainView.getLayoutParams();
|
||||
MarginLayoutParams sideParams = (MarginLayoutParams) mSideView.getLayoutParams();
|
||||
|
||||
int childLeft = pl + mainLp.leftMargin;
|
||||
int childTop = pt + mainLp.topMargin;
|
||||
int childRight = getWidth() - (pr + mainLp.rightMargin);
|
||||
int childBottom = getHeight() - (mainLp.bottomMargin + pb);
|
||||
mMainView.layout(childLeft, childTop, childRight, childBottom);
|
||||
|
||||
childLeft = childRight + sideParams.leftMargin;
|
||||
childTop = pt + sideParams.topMargin;
|
||||
childRight = childLeft + sideParams.leftMargin + sideParams.rightMargin + mSideView.getMeasuredWidth();
|
||||
childBottom = getHeight() - (sideParams.bottomMargin + pb);
|
||||
mSideView.layout(childLeft, childTop, childRight, childBottom);
|
||||
if (mOpenSwipe) {
|
||||
mMaxScrollOffset = mSideView.getWidth() + sideParams.leftMargin + sideParams.rightMargin;
|
||||
} else {
|
||||
mMaxScrollOffset = 0;
|
||||
}
|
||||
mScrollOffset = mScrollOffset < -mMaxScrollOffset / 2 ? -mMaxScrollOffset : 0;
|
||||
|
||||
offsetChildrenLeftAndRight(mScrollOffset);
|
||||
mInLayout = false;
|
||||
mIsLaidOut = true;
|
||||
}
|
||||
|
||||
void offsetChildrenLeftAndRight(int delta) {
|
||||
ViewCompat.offsetLeftAndRight(mMainView, delta);
|
||||
ViewCompat.offsetLeftAndRight(mSideView, delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
if (!mInLayout) {
|
||||
super.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutParams generateDefaultLayoutParams() {
|
||||
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutParams generateLayoutParams(LayoutParams p) {
|
||||
return p instanceof MarginLayoutParams ? p : new MarginLayoutParams(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(LayoutParams p) {
|
||||
return p instanceof MarginLayoutParams && super.checkLayoutParams(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutParams generateLayoutParams(AttributeSet attrs) {
|
||||
return new MarginLayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mScrollOffset != 0 && mIsLaidOut) {
|
||||
offsetChildrenLeftAndRight(-mScrollOffset);
|
||||
mScrollOffset = 0;
|
||||
} else
|
||||
mScrollOffset = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mScrollOffset != 0 && mIsLaidOut) {
|
||||
offsetChildrenLeftAndRight(-mScrollOffset);
|
||||
mScrollOffset = 0;
|
||||
} else
|
||||
mScrollOffset = 0;
|
||||
removeCallbacks(mScrollRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
//click main view,但是它处于open状态,所以,不需要点击效果,直接拦截不调用click listener
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
View pointView = findTopChildUnder(this, x, y);
|
||||
if (pointView != null && pointView == mMainView && mScrollOffset != 0)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
View pointView = findTopChildUnder(this, x, y);
|
||||
if (pointView != null && pointView == mMainView && mTouchMode == Mode.TAP && mScrollOffset != 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
//click main view,但是它处于open状态,所以,不需要点击效果,直接拦截不调用click listener
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
View pointView = findTopChildUnder(this, x, y);
|
||||
if (pointView != null && pointView == mMainView && mScrollOffset != 0)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
View pointView = findTopChildUnder(this, x, y);
|
||||
if (pointView != null && pointView == mMainView && mTouchMode == Mode.TAP && mScrollOffset != 0) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
if (getVisibility() != View.VISIBLE) {
|
||||
mScrollOffset = 0;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Interpolator sInterpolator = new Interpolator() {
|
||||
@Override
|
||||
public float getInterpolation(float t) {
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
}
|
||||
};
|
||||
|
||||
class ScrollRunnable implements Runnable {
|
||||
private static final int FLING_DURATION = 200;
|
||||
private Scroller mScroller;
|
||||
private boolean mAbort;
|
||||
private int mMinVelocity;
|
||||
private boolean mScrollToLeft;
|
||||
|
||||
ScrollRunnable(Context context) {
|
||||
mScroller = new Scroller(context, sInterpolator);
|
||||
mAbort = false;
|
||||
mScrollToLeft = false;
|
||||
|
||||
ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||
mMinVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||
}
|
||||
|
||||
void startScroll(int startX, int endX) {
|
||||
if (startX != endX) {
|
||||
// Log.e("scroll - startX - endX", "" + startX + " " + endX);
|
||||
setTouchMode(Mode.FLING);
|
||||
mAbort = false;
|
||||
mScrollToLeft = endX < startX;
|
||||
mScroller.startScroll(startX, 0, endX - startX, 0, 400);
|
||||
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
|
||||
}
|
||||
}
|
||||
|
||||
void startFling(int startX, int xVel) {
|
||||
// Log.e("fling - startX", "" + startX);
|
||||
|
||||
if (xVel > mMinVelocity && startX != 0) {
|
||||
startScroll(startX, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xVel < -mMinVelocity && startX != -mMaxScrollOffset) {
|
||||
startScroll(startX, -mMaxScrollOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
startScroll(startX, startX > -mMaxScrollOffset / 2 ? 0 : -mMaxScrollOffset);
|
||||
}
|
||||
|
||||
void abort() {
|
||||
if (!mAbort) {
|
||||
mAbort = true;
|
||||
if (!mScroller.isFinished()) {
|
||||
mScroller.abortAnimation();
|
||||
removeCallbacks(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//是否正在滑动需要另外判断
|
||||
boolean isScrollToLeft() {
|
||||
return mScrollToLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Log.e("abort", Boolean.toString(mAbort));
|
||||
if (!mAbort) {
|
||||
boolean more = mScroller.computeScrollOffset();
|
||||
int curX = mScroller.getCurrX();
|
||||
// Log.e("curX", "" + curX);
|
||||
|
||||
boolean atEdge = trackMotionScroll(curX - mScrollOffset);
|
||||
if (more && !atEdge) {
|
||||
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (atEdge) {
|
||||
removeCallbacks(this);
|
||||
if (!mScroller.isFinished())
|
||||
mScroller.abortAnimation();
|
||||
setTouchMode(Mode.RESET);
|
||||
}
|
||||
|
||||
if (!more) {
|
||||
setTouchMode(Mode.RESET);
|
||||
//绝对不会出现这种意外的!!!可以注释掉
|
||||
if (mScrollOffset != 0) {
|
||||
if (Math.abs(mScrollOffset) > mMaxScrollOffset / 2)
|
||||
mScrollOffset = -mMaxScrollOffset;
|
||||
else
|
||||
mScrollOffset = 0;
|
||||
ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OnSwipeItemTouchListener implements RecyclerView.OnItemTouchListener {
|
||||
private SwipeItemLayout mCaptureItem;
|
||||
private float mLastMotionX;
|
||||
private float mLastMotionY;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
|
||||
private int mActivePointerId;
|
||||
|
||||
private int mTouchSlop;
|
||||
private int mMaximumVelocity;
|
||||
|
||||
private boolean mDealByParent;
|
||||
private boolean mIsProbeParent;
|
||||
|
||||
public OnSwipeItemTouchListener(Context context) {
|
||||
ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
mActivePointerId = -1;
|
||||
mDealByParent = false;
|
||||
mIsProbeParent = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
|
||||
if (mIsProbeParent)
|
||||
return false;
|
||||
|
||||
boolean intercept = false;
|
||||
final int action = ev.getActionMasked();
|
||||
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
final float x = ev.getX();
|
||||
final float y = ev.getY();
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
|
||||
boolean pointOther = false;
|
||||
SwipeItemLayout pointItem = null;
|
||||
//首先知道ev针对的是哪个item
|
||||
View pointView = findTopChildUnder(rv, (int) x, (int) y);
|
||||
if (pointView == null || !(pointView instanceof SwipeItemLayout)) {
|
||||
//可能是head view或bottom view
|
||||
pointOther = true;
|
||||
} else
|
||||
pointItem = (SwipeItemLayout) pointView;
|
||||
|
||||
//此时的pointOther=true,意味着点击的view为空或者点击的不是item
|
||||
//还没有把点击的是item但是不是capture item给过滤出来
|
||||
if (!pointOther && (mCaptureItem == null || mCaptureItem != pointItem))
|
||||
pointOther = true;
|
||||
|
||||
//点击的是capture item
|
||||
if (!pointOther) {
|
||||
Mode touchMode = mCaptureItem.getTouchMode();
|
||||
|
||||
//如果它在fling,就转为drag
|
||||
//需要拦截,并且requestDisallowInterceptTouchEvent
|
||||
boolean disallowIntercept = false;
|
||||
if (touchMode == Mode.FLING) {
|
||||
mCaptureItem.setTouchMode(Mode.DRAG);
|
||||
disallowIntercept = true;
|
||||
intercept = true;
|
||||
} else {//如果是expand的,就不允许parent拦截
|
||||
mCaptureItem.setTouchMode(Mode.TAP);
|
||||
if (mCaptureItem.isOpen())
|
||||
disallowIntercept = true;
|
||||
}
|
||||
|
||||
if (disallowIntercept) {
|
||||
final ViewParent parent = rv.getParent();
|
||||
if (parent != null)
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
} else {//capture item为null或者与point item不一样
|
||||
//直接将其close掉
|
||||
if (mCaptureItem != null && mCaptureItem.isOpen()) {
|
||||
mCaptureItem.close();
|
||||
mCaptureItem = null;
|
||||
intercept = true;
|
||||
}
|
||||
|
||||
if (pointItem != null) {
|
||||
mCaptureItem = pointItem;
|
||||
mCaptureItem.setTouchMode(Mode.TAP);
|
||||
} else
|
||||
mCaptureItem = null;
|
||||
}
|
||||
|
||||
//如果parent处于fling状态,此时,parent就会转为drag。此时,应该将后续move都交给parent处理
|
||||
mIsProbeParent = true;
|
||||
mDealByParent = rv.onInterceptTouchEvent(ev);
|
||||
mIsProbeParent = false;
|
||||
if (mDealByParent)
|
||||
intercept = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
final int actionIndex = ev.getActionIndex();
|
||||
mActivePointerId = ev.getPointerId(actionIndex);
|
||||
|
||||
mLastMotionX = ev.getX(actionIndex);
|
||||
mLastMotionY = ev.getY(actionIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
final int actionIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(actionIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
final int newIndex = actionIndex == 0 ? 1 : 0;
|
||||
mActivePointerId = ev.getPointerId(newIndex);
|
||||
|
||||
mLastMotionX = ev.getX(newIndex);
|
||||
mLastMotionY = ev.getY(newIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (activePointerIndex == -1)
|
||||
break;
|
||||
|
||||
//在down时,就被认定为parent的drag,所以,直接交给parent处理即可
|
||||
if (mDealByParent) {
|
||||
if (mCaptureItem != null && mCaptureItem.isOpen())
|
||||
mCaptureItem.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
final int x = (int) (ev.getX(activePointerIndex) + .5f);
|
||||
final int y = (int) ((int) ev.getY(activePointerIndex) + .5f);
|
||||
|
||||
int deltaX = (int) (x - mLastMotionX);
|
||||
int deltaY = (int) (y - mLastMotionY);
|
||||
final int xDiff = Math.abs(deltaX);
|
||||
final int yDiff = Math.abs(deltaY);
|
||||
|
||||
if (mCaptureItem != null && !mDealByParent) {
|
||||
Mode touchMode = mCaptureItem.getTouchMode();
|
||||
|
||||
if (touchMode == Mode.TAP) {
|
||||
//如果capture item是open的,下拉有两种处理方式:
|
||||
// 1、下拉后,直接close item
|
||||
// 2、只要是open的,就拦截所有它的消息,这样如果点击open的,就只能滑动该capture item
|
||||
//网易邮箱,在open的情况下,下拉直接close
|
||||
//QQ,在open的情况下,下拉也是close。但是,做的不够好,没有达到该效果。
|
||||
if (xDiff > mTouchSlop && xDiff > yDiff) {
|
||||
mCaptureItem.setTouchMode(Mode.DRAG);
|
||||
final ViewParent parent = rv.getParent();
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
deltaX = deltaX > 0 ? deltaX - mTouchSlop : deltaX + mTouchSlop;
|
||||
} else {// if(yDiff>mTouchSlop){
|
||||
mIsProbeParent = true;
|
||||
boolean isParentConsume = rv.onInterceptTouchEvent(ev);
|
||||
mIsProbeParent = false;
|
||||
if (isParentConsume) {
|
||||
//表明不是水平滑动,即不判定为SwipeItemLayout的滑动
|
||||
//但是,可能是下拉刷新SwipeRefreshLayout或者RecyclerView的滑动
|
||||
//一般的下拉判定,都是yDiff>mTouchSlop,所以,此处这么写不会出问题
|
||||
//这里这么做以后,如果判定为下拉,就直接close
|
||||
mDealByParent = true;
|
||||
mCaptureItem.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touchMode = mCaptureItem.getTouchMode();
|
||||
if (touchMode == Mode.DRAG) {
|
||||
intercept = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
|
||||
//对capture item进行拖拽
|
||||
mCaptureItem.trackMotionScroll(deltaX);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mCaptureItem != null) {
|
||||
Mode touchMode = mCaptureItem.getTouchMode();
|
||||
if (touchMode == Mode.DRAG) {
|
||||
final VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
int xVel = (int) velocityTracker.getXVelocity(mActivePointerId);
|
||||
mCaptureItem.fling(xVel);
|
||||
|
||||
intercept = true;
|
||||
}
|
||||
}
|
||||
cancel();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mCaptureItem != null)
|
||||
mCaptureItem.revise();
|
||||
cancel();
|
||||
break;
|
||||
}
|
||||
|
||||
return intercept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
final int actionIndex = ev.getActionIndex();
|
||||
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
mActivePointerId = ev.getPointerId(actionIndex);
|
||||
|
||||
mLastMotionX = ev.getX(actionIndex);
|
||||
mLastMotionY = ev.getY(actionIndex);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
final int pointerId = ev.getPointerId(actionIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
final int newIndex = actionIndex == 0 ? 1 : 0;
|
||||
mActivePointerId = ev.getPointerId(newIndex);
|
||||
|
||||
mLastMotionX = ev.getX(newIndex);
|
||||
mLastMotionY = ev.getY(newIndex);
|
||||
}
|
||||
break;
|
||||
|
||||
//down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (activePointerIndex == -1)
|
||||
break;
|
||||
|
||||
final float x = ev.getX(activePointerIndex);
|
||||
final float y = (int) ev.getY(activePointerIndex);
|
||||
|
||||
int deltaX = (int) (x - mLastMotionX);
|
||||
|
||||
if (mCaptureItem != null && mCaptureItem.getTouchMode() == Mode.DRAG) {
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
|
||||
//对capture item进行拖拽
|
||||
mCaptureItem.trackMotionScroll(deltaX);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mCaptureItem != null) {
|
||||
Mode touchMode = mCaptureItem.getTouchMode();
|
||||
if (touchMode == Mode.DRAG) {
|
||||
final VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
int xVel = (int) velocityTracker.getXVelocity(mActivePointerId);
|
||||
mCaptureItem.fling(xVel);
|
||||
}
|
||||
}
|
||||
cancel();
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mCaptureItem != null)
|
||||
mCaptureItem.revise();
|
||||
|
||||
cancel();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
mDealByParent = false;
|
||||
mActivePointerId = -1;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static View findTopChildUnder(ViewGroup parent, int x, int y) {
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = childCount - 1; i >= 0; i--) {
|
||||
final View child = parent.getChildAt(i);
|
||||
if (x >= child.getLeft() && x < child.getRight()
|
||||
&& y >= child.getTop() && y < child.getBottom()) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void closeAllItems(RecyclerView recyclerView) {
|
||||
for (int i = 0; i < recyclerView.getChildCount(); i++) {
|
||||
View child = recyclerView.getChildAt(i);
|
||||
if (child instanceof SwipeItemLayout) {
|
||||
SwipeItemLayout swipeItemLayout = (SwipeItemLayout) child;
|
||||
if (swipeItemLayout.isOpen())
|
||||
swipeItemLayout.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.mogo.module.push.view.roundimage;
|
||||
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
Corner.TOP_LEFT, Corner.TOP_RIGHT,
|
||||
Corner.BOTTOM_LEFT, Corner.BOTTOM_RIGHT
|
||||
})
|
||||
public @interface Corner {
|
||||
int TOP_LEFT = 0;
|
||||
int TOP_RIGHT = 1;
|
||||
int BOTTOM_RIGHT = 2;
|
||||
int BOTTOM_LEFT = 3;
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Vincent Mi
|
||||
*
|
||||
* 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 com.mogo.module.push.view.roundimage;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class RoundedDrawable extends Drawable {
|
||||
|
||||
public static final String TAG = "RoundedDrawable";
|
||||
public static final int DEFAULT_BORDER_COLOR = Color.BLACK;
|
||||
|
||||
private final RectF mBounds = new RectF();
|
||||
private final RectF mDrawableRect = new RectF();
|
||||
private final RectF mBitmapRect = new RectF();
|
||||
private final Bitmap mBitmap;
|
||||
private final Paint mBitmapPaint;
|
||||
private final int mBitmapWidth;
|
||||
private final int mBitmapHeight;
|
||||
private final RectF mBorderRect = new RectF();
|
||||
private final Paint mBorderPaint;
|
||||
private final Matrix mShaderMatrix = new Matrix();
|
||||
private final RectF mSquareCornersRect = new RectF();
|
||||
|
||||
private Shader.TileMode mTileModeX = Shader.TileMode.CLAMP;
|
||||
private Shader.TileMode mTileModeY = Shader.TileMode.CLAMP;
|
||||
private boolean mRebuildShader = true;
|
||||
|
||||
// [ topLeft, topRight, bottomLeft, bottomRight ]
|
||||
private float mCornerRadius = 0f;
|
||||
private final boolean[] mCornersRounded = new boolean[] { true, true, true, true };
|
||||
|
||||
private boolean mOval = false;
|
||||
private float mBorderWidth = 0;
|
||||
private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||
private ScaleType mScaleType = ScaleType.FIT_CENTER;
|
||||
|
||||
public RoundedDrawable(Bitmap bitmap) {
|
||||
mBitmap = bitmap;
|
||||
|
||||
mBitmapWidth = bitmap.getWidth();
|
||||
mBitmapHeight = bitmap.getHeight();
|
||||
mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);
|
||||
|
||||
mBitmapPaint = new Paint();
|
||||
mBitmapPaint.setStyle(Paint.Style.FILL);
|
||||
mBitmapPaint.setAntiAlias(true);
|
||||
|
||||
mBorderPaint = new Paint();
|
||||
mBorderPaint.setStyle(Paint.Style.STROKE);
|
||||
mBorderPaint.setAntiAlias(true);
|
||||
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
|
||||
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||
}
|
||||
|
||||
public static RoundedDrawable fromBitmap(Bitmap bitmap) {
|
||||
if (bitmap != null) {
|
||||
return new RoundedDrawable(bitmap);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Drawable fromDrawable(Drawable drawable) {
|
||||
if (drawable != null) {
|
||||
if (drawable instanceof RoundedDrawable) {
|
||||
// just return if it's already a RoundedDrawable
|
||||
return drawable;
|
||||
} else if (drawable instanceof LayerDrawable) {
|
||||
LayerDrawable ld = (LayerDrawable) drawable;
|
||||
int num = ld.getNumberOfLayers();
|
||||
|
||||
// loop through layers to and change to RoundedDrawables if possible
|
||||
for (int i = 0; i < num; i++) {
|
||||
Drawable d = ld.getDrawable(i);
|
||||
ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d));
|
||||
}
|
||||
return ld;
|
||||
}
|
||||
|
||||
// try to get a bitmap from the drawable and
|
||||
Bitmap bm = drawableToBitmap(drawable);
|
||||
if (bm != null) {
|
||||
return new RoundedDrawable(bm);
|
||||
}
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public static Bitmap drawableToBitmap(Drawable drawable) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
int width = Math.max(drawable.getIntrinsicWidth(), 2);
|
||||
int height = Math.max(drawable.getIntrinsicHeight(), 2);
|
||||
try {
|
||||
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.w(TAG, "Failed to create bitmap from drawable!");
|
||||
bitmap = null;
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Bitmap getSourceBitmap() {
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return mBorderColor.isStateful();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
int newColor = mBorderColor.getColorForState(state, 0);
|
||||
if (mBorderPaint.getColor() != newColor) {
|
||||
mBorderPaint.setColor(newColor);
|
||||
return true;
|
||||
} else {
|
||||
return super.onStateChange(state);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShaderMatrix() {
|
||||
float scale;
|
||||
float dx;
|
||||
float dy;
|
||||
|
||||
switch (mScaleType) {
|
||||
case CENTER:
|
||||
mBorderRect.set(mBounds);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
|
||||
mShaderMatrix.reset();
|
||||
mShaderMatrix.setTranslate((int) ((mBorderRect.width() - mBitmapWidth) * 0.5f + 0.5f),
|
||||
(int) ((mBorderRect.height() - mBitmapHeight) * 0.5f + 0.5f));
|
||||
break;
|
||||
|
||||
case CENTER_CROP:
|
||||
mBorderRect.set(mBounds);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
|
||||
mShaderMatrix.reset();
|
||||
|
||||
dx = 0;
|
||||
dy = 0;
|
||||
|
||||
if (mBitmapWidth * mBorderRect.height() > mBorderRect.width() * mBitmapHeight) {
|
||||
scale = mBorderRect.height() / (float) mBitmapHeight;
|
||||
dx = (mBorderRect.width() - mBitmapWidth * scale) * 0.5f;
|
||||
} else {
|
||||
scale = mBorderRect.width() / (float) mBitmapWidth;
|
||||
dy = (mBorderRect.height() - mBitmapHeight * scale) * 0.5f;
|
||||
}
|
||||
|
||||
mShaderMatrix.setScale(scale, scale);
|
||||
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth / 2,
|
||||
(int) (dy + 0.5f) + mBorderWidth / 2);
|
||||
break;
|
||||
|
||||
case CENTER_INSIDE:
|
||||
mShaderMatrix.reset();
|
||||
|
||||
if (mBitmapWidth <= mBounds.width() && mBitmapHeight <= mBounds.height()) {
|
||||
scale = 1.0f;
|
||||
} else {
|
||||
scale = Math.min(mBounds.width() / (float) mBitmapWidth,
|
||||
mBounds.height() / (float) mBitmapHeight);
|
||||
}
|
||||
|
||||
dx = (int) ((mBounds.width() - mBitmapWidth * scale) * 0.5f + 0.5f);
|
||||
dy = (int) ((mBounds.height() - mBitmapHeight * scale) * 0.5f + 0.5f);
|
||||
|
||||
mShaderMatrix.setScale(scale, scale);
|
||||
mShaderMatrix.postTranslate(dx, dy);
|
||||
|
||||
mBorderRect.set(mBitmapRect);
|
||||
mShaderMatrix.mapRect(mBorderRect);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
|
||||
break;
|
||||
|
||||
default:
|
||||
case FIT_CENTER:
|
||||
mBorderRect.set(mBitmapRect);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.CENTER);
|
||||
mShaderMatrix.mapRect(mBorderRect);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
|
||||
break;
|
||||
|
||||
case FIT_END:
|
||||
mBorderRect.set(mBitmapRect);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.END);
|
||||
mShaderMatrix.mapRect(mBorderRect);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
|
||||
break;
|
||||
|
||||
case FIT_START:
|
||||
mBorderRect.set(mBitmapRect);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.START);
|
||||
mShaderMatrix.mapRect(mBorderRect);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
|
||||
break;
|
||||
|
||||
case FIT_XY:
|
||||
mBorderRect.set(mBounds);
|
||||
mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);
|
||||
mShaderMatrix.reset();
|
||||
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL);
|
||||
break;
|
||||
}
|
||||
|
||||
mDrawableRect.set(mBorderRect);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(@NonNull Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
|
||||
mBounds.set(bounds);
|
||||
|
||||
updateShaderMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (mRebuildShader) {
|
||||
BitmapShader bitmapShader = new BitmapShader(mBitmap, mTileModeX, mTileModeY);
|
||||
if (mTileModeX == Shader.TileMode.CLAMP && mTileModeY == Shader.TileMode.CLAMP) {
|
||||
bitmapShader.setLocalMatrix(mShaderMatrix);
|
||||
}
|
||||
mBitmapPaint.setShader(bitmapShader);
|
||||
mRebuildShader = false;
|
||||
}
|
||||
|
||||
if (mOval) {
|
||||
if (mBorderWidth > 0) {
|
||||
canvas.drawOval(mDrawableRect, mBitmapPaint);
|
||||
canvas.drawOval(mBorderRect, mBorderPaint);
|
||||
} else {
|
||||
canvas.drawOval(mDrawableRect, mBitmapPaint);
|
||||
}
|
||||
} else {
|
||||
if (any(mCornersRounded)) {
|
||||
float radius = mCornerRadius;
|
||||
if (mBorderWidth > 0) {
|
||||
canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
|
||||
canvas.drawRoundRect(mBorderRect, radius, radius, mBorderPaint);
|
||||
redrawBitmapForSquareCorners(canvas);
|
||||
redrawBorderForSquareCorners(canvas);
|
||||
} else {
|
||||
canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
|
||||
redrawBitmapForSquareCorners(canvas);
|
||||
}
|
||||
} else {
|
||||
canvas.drawRect(mDrawableRect, mBitmapPaint);
|
||||
if (mBorderWidth > 0) {
|
||||
canvas.drawRect(mBorderRect, mBorderPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void redrawBitmapForSquareCorners(Canvas canvas) {
|
||||
if (all(mCornersRounded)) {
|
||||
// no square corners
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCornerRadius == 0) {
|
||||
return; // no round corners
|
||||
}
|
||||
|
||||
float left = mDrawableRect.left;
|
||||
float top = mDrawableRect.top;
|
||||
float right = left + mDrawableRect.width();
|
||||
float bottom = top + mDrawableRect.height();
|
||||
float radius = mCornerRadius;
|
||||
|
||||
if (!mCornersRounded[Corner.TOP_LEFT]) {
|
||||
mSquareCornersRect.set(left, top, left + radius, top + radius);
|
||||
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
|
||||
}
|
||||
|
||||
if (!mCornersRounded[Corner.TOP_RIGHT]) {
|
||||
mSquareCornersRect.set(right - radius, top, right, radius);
|
||||
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
|
||||
}
|
||||
|
||||
if (!mCornersRounded[Corner.BOTTOM_RIGHT]) {
|
||||
mSquareCornersRect.set(right - radius, bottom - radius, right, bottom);
|
||||
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
|
||||
}
|
||||
|
||||
if (!mCornersRounded[Corner.BOTTOM_LEFT]) {
|
||||
mSquareCornersRect.set(left, bottom - radius, left + radius, bottom);
|
||||
canvas.drawRect(mSquareCornersRect, mBitmapPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void redrawBorderForSquareCorners(Canvas canvas) {
|
||||
if (all(mCornersRounded)) {
|
||||
// no square corners
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCornerRadius == 0) {
|
||||
return; // no round corners
|
||||
}
|
||||
|
||||
float left = mDrawableRect.left;
|
||||
float top = mDrawableRect.top;
|
||||
float right = left + mDrawableRect.width();
|
||||
float bottom = top + mDrawableRect.height();
|
||||
float radius = mCornerRadius;
|
||||
float offset = mBorderWidth / 2;
|
||||
|
||||
if (!mCornersRounded[Corner.TOP_LEFT]) {
|
||||
canvas.drawLine(left - offset, top, left + radius, top, mBorderPaint);
|
||||
canvas.drawLine(left, top - offset, left, top + radius, mBorderPaint);
|
||||
}
|
||||
|
||||
if (!mCornersRounded[Corner.TOP_RIGHT]) {
|
||||
canvas.drawLine(right - radius - offset, top, right, top, mBorderPaint);
|
||||
canvas.drawLine(right, top - offset, right, top + radius, mBorderPaint);
|
||||
}
|
||||
|
||||
if (!mCornersRounded[Corner.BOTTOM_RIGHT]) {
|
||||
canvas.drawLine(right - radius - offset, bottom, right + offset, bottom, mBorderPaint);
|
||||
canvas.drawLine(right, bottom - radius, right, bottom, mBorderPaint);
|
||||
}
|
||||
|
||||
if (!mCornersRounded[Corner.BOTTOM_LEFT]) {
|
||||
canvas.drawLine(left - offset, bottom, left + radius, bottom, mBorderPaint);
|
||||
canvas.drawLine(left, bottom - radius, left, bottom, mBorderPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mBitmapPaint.getAlpha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mBitmapPaint.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorFilter getColorFilter() {
|
||||
return mBitmapPaint.getColorFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mBitmapPaint.setColorFilter(cf);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDither(boolean dither) {
|
||||
mBitmapPaint.setDither(dither);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filter) {
|
||||
mBitmapPaint.setFilterBitmap(filter);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mBitmapWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mBitmapHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the corner radius.
|
||||
*/
|
||||
public float getCornerRadius() {
|
||||
return mCornerRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param corner the specific corner to get radius of.
|
||||
* @return the corner radius of the specified corner.
|
||||
*/
|
||||
public float getCornerRadius(@Corner int corner) {
|
||||
return mCornersRounded[corner] ? mCornerRadius : 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all corners to the specified radius.
|
||||
*
|
||||
* @param radius the radius.
|
||||
* @return the {@link RoundedDrawable} for chaining.
|
||||
*/
|
||||
public RoundedDrawable setCornerRadius(float radius) {
|
||||
setCornerRadius(radius, radius, radius, radius);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the corner radius of one specific corner.
|
||||
*
|
||||
* @param corner the corner.
|
||||
* @param radius the radius.
|
||||
* @return the {@link RoundedDrawable} for chaining.
|
||||
*/
|
||||
public RoundedDrawable setCornerRadius(@Corner int corner, float radius) {
|
||||
if (radius != 0 && mCornerRadius != 0 && mCornerRadius != radius) {
|
||||
throw new IllegalArgumentException("Multiple nonzero corner radii not yet supported.");
|
||||
}
|
||||
|
||||
if (radius == 0) {
|
||||
if (only(corner, mCornersRounded)) {
|
||||
mCornerRadius = 0;
|
||||
}
|
||||
mCornersRounded[corner] = false;
|
||||
} else {
|
||||
if (mCornerRadius == 0) {
|
||||
mCornerRadius = radius;
|
||||
}
|
||||
mCornersRounded[corner] = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the corner radii of all the corners.
|
||||
*
|
||||
* @param topLeft top left corner radius.
|
||||
* @param topRight top right corner radius
|
||||
* @param bottomRight bototm right corner radius.
|
||||
* @param bottomLeft bottom left corner radius.
|
||||
* @return the {@link RoundedDrawable} for chaining.
|
||||
*/
|
||||
public RoundedDrawable setCornerRadius(float topLeft, float topRight, float bottomRight,
|
||||
float bottomLeft) {
|
||||
Set<Float> radiusSet = new HashSet<>(4);
|
||||
radiusSet.add(topLeft);
|
||||
radiusSet.add(topRight);
|
||||
radiusSet.add(bottomRight);
|
||||
radiusSet.add(bottomLeft);
|
||||
|
||||
radiusSet.remove(0f);
|
||||
|
||||
if (radiusSet.size() > 1) {
|
||||
throw new IllegalArgumentException("Multiple nonzero corner radii not yet supported.");
|
||||
}
|
||||
|
||||
if (!radiusSet.isEmpty()) {
|
||||
float radius = radiusSet.iterator().next();
|
||||
if (Float.isInfinite(radius) || Float.isNaN(radius) || radius < 0) {
|
||||
throw new IllegalArgumentException("Invalid radius value: " + radius);
|
||||
}
|
||||
mCornerRadius = radius;
|
||||
} else {
|
||||
mCornerRadius = 0f;
|
||||
}
|
||||
|
||||
mCornersRounded[Corner.TOP_LEFT] = topLeft > 0;
|
||||
mCornersRounded[Corner.TOP_RIGHT] = topRight > 0;
|
||||
mCornersRounded[Corner.BOTTOM_RIGHT] = bottomRight > 0;
|
||||
mCornersRounded[Corner.BOTTOM_LEFT] = bottomLeft > 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getBorderWidth() {
|
||||
return mBorderWidth;
|
||||
}
|
||||
|
||||
public RoundedDrawable setBorderWidth(float width) {
|
||||
mBorderWidth = width;
|
||||
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getBorderColor() {
|
||||
return mBorderColor.getDefaultColor();
|
||||
}
|
||||
|
||||
public RoundedDrawable setBorderColor(@ColorInt int color) {
|
||||
return setBorderColor(ColorStateList.valueOf(color));
|
||||
}
|
||||
|
||||
public ColorStateList getBorderColors() {
|
||||
return mBorderColor;
|
||||
}
|
||||
|
||||
public RoundedDrawable setBorderColor(ColorStateList colors) {
|
||||
mBorderColor = colors != null ? colors : ColorStateList.valueOf(0);
|
||||
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isOval() {
|
||||
return mOval;
|
||||
}
|
||||
|
||||
public RoundedDrawable setOval(boolean oval) {
|
||||
mOval = oval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScaleType getScaleType() {
|
||||
return mScaleType;
|
||||
}
|
||||
|
||||
public RoundedDrawable setScaleType(ScaleType scaleType) {
|
||||
if (scaleType == null) {
|
||||
scaleType = ScaleType.FIT_CENTER;
|
||||
}
|
||||
if (mScaleType != scaleType) {
|
||||
mScaleType = scaleType;
|
||||
updateShaderMatrix();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shader.TileMode getTileModeX() {
|
||||
return mTileModeX;
|
||||
}
|
||||
|
||||
public RoundedDrawable setTileModeX(Shader.TileMode tileModeX) {
|
||||
if (mTileModeX != tileModeX) {
|
||||
mTileModeX = tileModeX;
|
||||
mRebuildShader = true;
|
||||
invalidateSelf();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shader.TileMode getTileModeY() {
|
||||
return mTileModeY;
|
||||
}
|
||||
|
||||
public RoundedDrawable setTileModeY(Shader.TileMode tileModeY) {
|
||||
if (mTileModeY != tileModeY) {
|
||||
mTileModeY = tileModeY;
|
||||
mRebuildShader = true;
|
||||
invalidateSelf();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean only(int index, boolean[] booleans) {
|
||||
for (int i = 0, len = booleans.length; i < len; i++) {
|
||||
if (booleans[i] != (i == index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean any(boolean[] booleans) {
|
||||
for (boolean b : booleans) {
|
||||
if (b) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean all(boolean[] booleans) {
|
||||
for (boolean b : booleans) {
|
||||
if (b) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Bitmap toBitmap() {
|
||||
return drawableToBitmap(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Vincent Mi
|
||||
*
|
||||
* 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 com.mogo.module.push.view.roundimage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import com.mogo.module.push.R;
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class RoundedImageView extends AppCompatImageView {
|
||||
|
||||
// Constants for tile mode attributes
|
||||
private static final int TILE_MODE_UNDEFINED = -2;
|
||||
private static final int TILE_MODE_CLAMP = 0;
|
||||
private static final int TILE_MODE_REPEAT = 1;
|
||||
private static final int TILE_MODE_MIRROR = 2;
|
||||
|
||||
public static final String TAG = "RoundedImageView";
|
||||
public static final float DEFAULT_RADIUS = 0f;
|
||||
public static final float DEFAULT_BORDER_WIDTH = 0f;
|
||||
public static final Shader.TileMode DEFAULT_TILE_MODE = Shader.TileMode.CLAMP;
|
||||
private static final ImageView.ScaleType[] SCALE_TYPES = {
|
||||
ImageView.ScaleType.MATRIX,
|
||||
ImageView.ScaleType.FIT_XY,
|
||||
ImageView.ScaleType.FIT_START,
|
||||
ImageView.ScaleType.FIT_CENTER,
|
||||
ImageView.ScaleType.FIT_END,
|
||||
ImageView.ScaleType.CENTER,
|
||||
ImageView.ScaleType.CENTER_CROP,
|
||||
ImageView.ScaleType.CENTER_INSIDE
|
||||
};
|
||||
|
||||
private final float[] mCornerRadii =
|
||||
new float[] { DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS, DEFAULT_RADIUS };
|
||||
|
||||
private Drawable mBackgroundDrawable;
|
||||
private ColorStateList mBorderColor =
|
||||
ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
|
||||
private float mBorderWidth = DEFAULT_BORDER_WIDTH;
|
||||
private ColorFilter mColorFilter = null;
|
||||
private boolean mColorMod = false;
|
||||
private Drawable mDrawable;
|
||||
private boolean mHasColorFilter = false;
|
||||
private boolean mIsOval = false;
|
||||
private boolean mMutateBackground = false;
|
||||
private int mResource;
|
||||
private int mBackgroundResource;
|
||||
private ImageView.ScaleType mScaleType;
|
||||
private Shader.TileMode mTileModeX = DEFAULT_TILE_MODE;
|
||||
private Shader.TileMode mTileModeY = DEFAULT_TILE_MODE;
|
||||
|
||||
public RoundedImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundedImageView, defStyle, 0);
|
||||
|
||||
int index = a.getInt(R.styleable.RoundedImageView_android_scaleType, -1);
|
||||
if (index >= 0) {
|
||||
setScaleType(SCALE_TYPES[index]);
|
||||
} else {
|
||||
// default scaletype to FIT_CENTER
|
||||
setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
}
|
||||
|
||||
float cornerRadiusOverride =
|
||||
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius, -1);
|
||||
|
||||
mCornerRadii[Corner.TOP_LEFT] =
|
||||
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_top_left, -1);
|
||||
mCornerRadii[Corner.TOP_RIGHT] =
|
||||
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_top_right, -1);
|
||||
mCornerRadii[Corner.BOTTOM_RIGHT] =
|
||||
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_bottom_right, -1);
|
||||
mCornerRadii[Corner.BOTTOM_LEFT] =
|
||||
a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_corner_radius_bottom_left, -1);
|
||||
|
||||
boolean any = false;
|
||||
for (int i = 0, len = mCornerRadii.length; i < len; i++) {
|
||||
if (mCornerRadii[i] < 0) {
|
||||
mCornerRadii[i] = 0f;
|
||||
} else {
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any) {
|
||||
if (cornerRadiusOverride < 0) {
|
||||
cornerRadiusOverride = DEFAULT_RADIUS;
|
||||
}
|
||||
for (int i = 0, len = mCornerRadii.length; i < len; i++) {
|
||||
mCornerRadii[i] = cornerRadiusOverride;
|
||||
}
|
||||
}
|
||||
|
||||
mBorderWidth = a.getDimensionPixelSize(R.styleable.RoundedImageView_riv_border_width, -1);
|
||||
if (mBorderWidth < 0) {
|
||||
mBorderWidth = DEFAULT_BORDER_WIDTH;
|
||||
}
|
||||
|
||||
mBorderColor = a.getColorStateList(R.styleable.RoundedImageView_riv_border_color);
|
||||
if (mBorderColor == null) {
|
||||
mBorderColor = ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
|
||||
}
|
||||
|
||||
mMutateBackground = a.getBoolean(R.styleable.RoundedImageView_riv_mutate_background, false);
|
||||
mIsOval = a.getBoolean(R.styleable.RoundedImageView_riv_oval, false);
|
||||
|
||||
final int tileMode = a.getInt(R.styleable.RoundedImageView_riv_tile_Mode, TILE_MODE_UNDEFINED);
|
||||
if (tileMode != TILE_MODE_UNDEFINED) {
|
||||
setTileModeX(parseTileMode(tileMode));
|
||||
setTileModeY(parseTileMode(tileMode));
|
||||
}
|
||||
|
||||
final int tileModeX =
|
||||
a.getInt(R.styleable.RoundedImageView_riv_tile_Mode_x, TILE_MODE_UNDEFINED);
|
||||
if (tileModeX != TILE_MODE_UNDEFINED) {
|
||||
setTileModeX(parseTileMode(tileModeX));
|
||||
}
|
||||
|
||||
final int tileModeY =
|
||||
a.getInt(R.styleable.RoundedImageView_riv_tile_Mode_y, TILE_MODE_UNDEFINED);
|
||||
if (tileModeY != TILE_MODE_UNDEFINED) {
|
||||
setTileModeY(parseTileMode(tileModeY));
|
||||
}
|
||||
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(true);
|
||||
|
||||
if (mMutateBackground) {
|
||||
// when setBackground() is called by View constructor, mMutateBackground is not loaded from the attribute,
|
||||
// so it's false by default, what doesn't allow to create the RoundedDrawable. At this point, after load
|
||||
// mMutateBackground and updated BackgroundDrawable to RoundedDrawable, the View's background drawable needs to
|
||||
// be changed to this new drawable.
|
||||
//noinspection deprecation
|
||||
super.setBackgroundDrawable(mBackgroundDrawable);
|
||||
}
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
private static Shader.TileMode parseTileMode(int tileMode) {
|
||||
switch (tileMode) {
|
||||
case TILE_MODE_CLAMP:
|
||||
return Shader.TileMode.CLAMP;
|
||||
case TILE_MODE_REPEAT:
|
||||
return Shader.TileMode.REPEAT;
|
||||
case TILE_MODE_MIRROR:
|
||||
return Shader.TileMode.MIRROR;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageView.ScaleType getScaleType() {
|
||||
return mScaleType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleType(ImageView.ScaleType scaleType) {
|
||||
assert scaleType != null;
|
||||
|
||||
if (mScaleType != scaleType) {
|
||||
mScaleType = scaleType;
|
||||
|
||||
switch (scaleType) {
|
||||
case CENTER:
|
||||
case CENTER_CROP:
|
||||
case CENTER_INSIDE:
|
||||
case FIT_CENTER:
|
||||
case FIT_START:
|
||||
case FIT_END:
|
||||
case FIT_XY:
|
||||
super.setScaleType(ImageView.ScaleType.FIT_XY);
|
||||
break;
|
||||
default:
|
||||
super.setScaleType(scaleType);
|
||||
break;
|
||||
}
|
||||
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
mResource = 0;
|
||||
mDrawable = RoundedDrawable.fromDrawable(drawable);
|
||||
updateDrawableAttrs();
|
||||
super.setImageDrawable(mDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageBitmap(Bitmap bm) {
|
||||
mResource = 0;
|
||||
mDrawable = RoundedDrawable.fromBitmap(bm);
|
||||
updateDrawableAttrs();
|
||||
super.setImageDrawable(mDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageResource(@DrawableRes int resId) {
|
||||
if (mResource != resId) {
|
||||
mResource = resId;
|
||||
mDrawable = resolveResource();
|
||||
updateDrawableAttrs();
|
||||
super.setImageDrawable(mDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void setImageURI(Uri uri) {
|
||||
super.setImageURI(uri);
|
||||
setImageDrawable(getDrawable());
|
||||
}
|
||||
|
||||
private Drawable resolveResource() {
|
||||
Resources rsrc = getResources();
|
||||
if (rsrc == null) { return null; }
|
||||
|
||||
Drawable d = null;
|
||||
|
||||
if (mResource != 0) {
|
||||
try {
|
||||
d = rsrc.getDrawable(mResource);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Unable to find resource: " + mResource, e);
|
||||
// Don't try again.
|
||||
mResource = 0;
|
||||
}
|
||||
}
|
||||
return RoundedDrawable.fromDrawable(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Drawable background) {
|
||||
setBackgroundDrawable(background);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundResource(@DrawableRes int resId) {
|
||||
if (mBackgroundResource != resId) {
|
||||
mBackgroundResource = resId;
|
||||
mBackgroundDrawable = resolveBackgroundResource();
|
||||
setBackgroundDrawable(mBackgroundDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(int color) {
|
||||
mBackgroundDrawable = new ColorDrawable(color);
|
||||
setBackgroundDrawable(mBackgroundDrawable);
|
||||
}
|
||||
|
||||
private Drawable resolveBackgroundResource() {
|
||||
Resources rsrc = getResources();
|
||||
if (rsrc == null) { return null; }
|
||||
|
||||
Drawable d = null;
|
||||
|
||||
if (mBackgroundResource != 0) {
|
||||
try {
|
||||
d = rsrc.getDrawable(mBackgroundResource);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Unable to find resource: " + mBackgroundResource, e);
|
||||
// Don't try again.
|
||||
mBackgroundResource = 0;
|
||||
}
|
||||
}
|
||||
return RoundedDrawable.fromDrawable(d);
|
||||
}
|
||||
|
||||
private void updateDrawableAttrs() {
|
||||
updateAttrs(mDrawable, mScaleType);
|
||||
}
|
||||
|
||||
private void updateBackgroundDrawableAttrs(boolean convert) {
|
||||
if (mMutateBackground) {
|
||||
if (convert) {
|
||||
mBackgroundDrawable = RoundedDrawable.fromDrawable(mBackgroundDrawable);
|
||||
}
|
||||
updateAttrs(mBackgroundDrawable, ImageView.ScaleType.FIT_XY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void setColorFilter(ColorFilter cf) {
|
||||
if (mColorFilter != cf) {
|
||||
mColorFilter = cf;
|
||||
mHasColorFilter = true;
|
||||
mColorMod = true;
|
||||
applyColorMod();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyColorMod() {
|
||||
// Only mutate and apply when modifications have occurred. This should
|
||||
// not reset the mColorMod flag, since these filters need to be
|
||||
// re-applied if the Drawable is changed.
|
||||
if (mDrawable != null && mColorMod) {
|
||||
mDrawable = mDrawable.mutate();
|
||||
if (mHasColorFilter) {
|
||||
mDrawable.setColorFilter(mColorFilter);
|
||||
}
|
||||
//mDrawable.setXfermode(mXfermode);
|
||||
//mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAttrs(Drawable drawable, ImageView.ScaleType scaleType) {
|
||||
if (drawable == null) { return; }
|
||||
|
||||
if (drawable instanceof RoundedDrawable) {
|
||||
((RoundedDrawable) drawable)
|
||||
.setScaleType(scaleType)
|
||||
.setBorderWidth(mBorderWidth)
|
||||
.setBorderColor(mBorderColor)
|
||||
.setOval(mIsOval)
|
||||
.setTileModeX(mTileModeX)
|
||||
.setTileModeY(mTileModeY);
|
||||
|
||||
if (mCornerRadii != null) {
|
||||
((RoundedDrawable) drawable).setCornerRadius(
|
||||
mCornerRadii[Corner.TOP_LEFT],
|
||||
mCornerRadii[Corner.TOP_RIGHT],
|
||||
mCornerRadii[Corner.BOTTOM_RIGHT],
|
||||
mCornerRadii[Corner.BOTTOM_LEFT]);
|
||||
}
|
||||
|
||||
applyColorMod();
|
||||
} else if (drawable instanceof LayerDrawable) {
|
||||
// loop through layers to and set drawable attrs
|
||||
LayerDrawable ld = ((LayerDrawable) drawable);
|
||||
for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) {
|
||||
updateAttrs(ld.getDrawable(i), scaleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setBackgroundDrawable(Drawable background) {
|
||||
mBackgroundDrawable = background;
|
||||
updateBackgroundDrawableAttrs(true);
|
||||
//noinspection deprecation
|
||||
super.setBackgroundDrawable(mBackgroundDrawable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the largest corner radius.
|
||||
*/
|
||||
public float getCornerRadius() {
|
||||
return getMaxCornerRadius();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the largest corner radius.
|
||||
*/
|
||||
public float getMaxCornerRadius() {
|
||||
float maxRadius = 0;
|
||||
for (float r : mCornerRadii) {
|
||||
maxRadius = Math.max(r, maxRadius);
|
||||
}
|
||||
return maxRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corner radius of a specified corner.
|
||||
*
|
||||
* @param corner the corner.
|
||||
* @return the radius.
|
||||
*/
|
||||
public float getCornerRadius(@Corner int corner) {
|
||||
return mCornerRadii[corner];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all the corner radii from a dimension resource id.
|
||||
*
|
||||
* @param resId dimension resource id of radii.
|
||||
*/
|
||||
public void setCornerRadiusDimen(@DimenRes int resId) {
|
||||
float radius = getResources().getDimension(resId);
|
||||
setCornerRadius(radius, radius, radius, radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the corner radius of a specific corner from a dimension resource id.
|
||||
*
|
||||
* @param corner the corner to set.
|
||||
* @param resId the dimension resource id of the corner radius.
|
||||
*/
|
||||
public void setCornerRadiusDimen(@Corner int corner, @DimenRes int resId) {
|
||||
setCornerRadius(corner, getResources().getDimensionPixelSize(resId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the corner radii of all corners in px.
|
||||
*
|
||||
* @param radius the radius to set.
|
||||
*/
|
||||
public void setCornerRadius(float radius) {
|
||||
setCornerRadius(radius, radius, radius, radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the corner radius of a specific corner in px.
|
||||
*
|
||||
* @param corner the corner to set.
|
||||
* @param radius the corner radius to set in px.
|
||||
*/
|
||||
public void setCornerRadius(@Corner int corner, float radius) {
|
||||
if (mCornerRadii[corner] == radius) {
|
||||
return;
|
||||
}
|
||||
mCornerRadii[corner] = radius;
|
||||
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the corner radii of each corner individually. Currently only one unique nonzero value is
|
||||
* supported.
|
||||
*
|
||||
* @param topLeft radius of the top left corner in px.
|
||||
* @param topRight radius of the top right corner in px.
|
||||
* @param bottomRight radius of the bottom right corner in px.
|
||||
* @param bottomLeft radius of the bottom left corner in px.
|
||||
*/
|
||||
public void setCornerRadius(float topLeft, float topRight, float bottomLeft, float bottomRight) {
|
||||
if (mCornerRadii[Corner.TOP_LEFT] == topLeft
|
||||
&& mCornerRadii[Corner.TOP_RIGHT] == topRight
|
||||
&& mCornerRadii[Corner.BOTTOM_RIGHT] == bottomRight
|
||||
&& mCornerRadii[Corner.BOTTOM_LEFT] == bottomLeft) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCornerRadii[Corner.TOP_LEFT] = topLeft;
|
||||
mCornerRadii[Corner.TOP_RIGHT] = topRight;
|
||||
mCornerRadii[Corner.BOTTOM_LEFT] = bottomLeft;
|
||||
mCornerRadii[Corner.BOTTOM_RIGHT] = bottomRight;
|
||||
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public float getBorderWidth() {
|
||||
return mBorderWidth;
|
||||
}
|
||||
|
||||
public void setBorderWidth(@DimenRes int resId) {
|
||||
setBorderWidth(getResources().getDimension(resId));
|
||||
}
|
||||
|
||||
public void setBorderWidth(float width) {
|
||||
if (mBorderWidth == width) { return; }
|
||||
|
||||
mBorderWidth = width;
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public int getBorderColor() {
|
||||
return mBorderColor.getDefaultColor();
|
||||
}
|
||||
|
||||
public void setBorderColor(@ColorInt int color) {
|
||||
setBorderColor(ColorStateList.valueOf(color));
|
||||
}
|
||||
|
||||
public ColorStateList getBorderColors() {
|
||||
return mBorderColor;
|
||||
}
|
||||
|
||||
public void setBorderColor(ColorStateList colors) {
|
||||
if (mBorderColor.equals(colors)) { return; }
|
||||
|
||||
mBorderColor =
|
||||
(colors != null) ? colors : ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR);
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
if (mBorderWidth > 0) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOval() {
|
||||
return mIsOval;
|
||||
}
|
||||
|
||||
public void setOval(boolean oval) {
|
||||
mIsOval = oval;
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public Shader.TileMode getTileModeX() {
|
||||
return mTileModeX;
|
||||
}
|
||||
|
||||
public void setTileModeX(Shader.TileMode tileModeX) {
|
||||
if (this.mTileModeX == tileModeX) { return; }
|
||||
|
||||
this.mTileModeX = tileModeX;
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public Shader.TileMode getTileModeY() {
|
||||
return mTileModeY;
|
||||
}
|
||||
|
||||
public void setTileModeY(Shader.TileMode tileModeY) {
|
||||
if (this.mTileModeY == tileModeY) { return; }
|
||||
|
||||
this.mTileModeY = tileModeY;
|
||||
updateDrawableAttrs();
|
||||
updateBackgroundDrawableAttrs(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean mutatesBackground() {
|
||||
return mMutateBackground;
|
||||
}
|
||||
|
||||
public void mutateBackground(boolean mutate) {
|
||||
if (mMutateBackground == mutate) { return; }
|
||||
|
||||
mMutateBackground = mutate;
|
||||
updateBackgroundDrawableAttrs(true);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.mogo.module.push.viewmodel
|
||||
|
||||
import com.mogo.module.push.model.PushBean
|
||||
import com.mogo.module.push.repository.PushRepository
|
||||
import com.mogo.module.push.utils.HandlerUtils
|
||||
|
||||
class MessageViewModel(private val messageViewModel: MessageListChange) {
|
||||
|
||||
var list: MutableList<PushBean>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
messageViewModel.messageListChange(value)
|
||||
}
|
||||
|
||||
init {
|
||||
HandlerUtils.mBgHandler.post {
|
||||
list = PushRepository.pushRepository.getAll()
|
||||
}
|
||||
}
|
||||
|
||||
interface MessageListChange {
|
||||
fun messageListChange(list: MutableList<PushBean>?)
|
||||
}
|
||||
|
||||
fun delete(bean: PushBean) {
|
||||
PushRepository.pushRepository.delete(bean)
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
list = null
|
||||
PushRepository.pushRepository.deleteAll()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.mogo.module.push.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.mogo.commons.voice.AIAssist
|
||||
import com.mogo.commons.voice.IMogoVoiceCmdCallBack
|
||||
import com.mogo.module.push.Config
|
||||
import com.zhidao.auto.platform.voice.VoiceClient
|
||||
import com.mogo.module.push.model.PushBean
|
||||
import com.mogo.module.push.repository.PushRepository
|
||||
import com.mogo.module.push.utils.AnalyticsUtils
|
||||
import com.mogo.module.push.utils.dealSchema
|
||||
import com.mogo.module.push.view.FloatView
|
||||
|
||||
class PushViewModel(
|
||||
private val mContext: Context,
|
||||
private val pushRepository: PushRepository
|
||||
) {
|
||||
companion object {
|
||||
const val VOICE_ACTION_PUSH_MAIN = "VOICE_ACTION_PUSH_MAIN"
|
||||
const val VOICE_ACTION_PUSH_LEFT = "VOICE_ACTION_PUSH_LEFT"
|
||||
const val VOICE_ACTION_PUSH_RIGHT = "VOICE_ACTION_PUSH_RIGHT"
|
||||
const val VOICE_ACTION_PUSH_CANCEL = "VOICE_ACTION_PUSH_CANCEL"
|
||||
}
|
||||
|
||||
var floatView: FloatView? = null
|
||||
|
||||
private var mVoiceClient: AIAssist = AIAssist.getInstance(mContext)
|
||||
|
||||
private val voiceCmdCallback = object : IMogoVoiceCmdCallBack {
|
||||
override fun onSpeakEnd(speakText: String?) {
|
||||
}
|
||||
|
||||
override fun onCmdSelected(cmd: String?) {
|
||||
if (!cmd.isNullOrEmpty()) {
|
||||
dealCmd(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCmdAction(speakText: String?) {
|
||||
}
|
||||
|
||||
override fun onCmdCancel(speakText: String?) {
|
||||
}
|
||||
|
||||
override fun onSpeakSelectTimeOut(speakText: String?) {
|
||||
}
|
||||
}
|
||||
|
||||
fun dealCmd(cmd: String, isClick: String = "2") {
|
||||
if (TextUtils.isEmpty(cmd)) {
|
||||
return
|
||||
}
|
||||
pushBean?.let {
|
||||
when (cmd) {
|
||||
VOICE_ACTION_PUSH_MAIN -> {
|
||||
AnalyticsUtils.track(Config.NEWS_CARD_CLICK, "trigger_type", isClick)
|
||||
if (isClick == "2") {
|
||||
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
|
||||
}
|
||||
if (!it.mainSchema.isNullOrEmpty()) {
|
||||
dealSchema(it.mainSchema, mContext)
|
||||
}
|
||||
}
|
||||
VOICE_ACTION_PUSH_LEFT -> {
|
||||
if (it.buttons.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
AnalyticsUtils.track(
|
||||
Config.NEWS_CARD_CLICK_BTN,
|
||||
"trigger_type",
|
||||
isClick, "btn_text", it.buttons[0].text
|
||||
)
|
||||
if (isClick == "2") {
|
||||
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
|
||||
}
|
||||
if (!it.buttons[0].action.isNullOrEmpty()) {
|
||||
dealSchema(it.buttons[0].action, mContext)
|
||||
}
|
||||
}
|
||||
VOICE_ACTION_PUSH_RIGHT -> {
|
||||
if (it.buttons.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
AnalyticsUtils.track(
|
||||
Config.NEWS_CARD_CLICK_BTN,
|
||||
"trigger_type",
|
||||
isClick, "btn_text", it.buttons[1].text
|
||||
)
|
||||
if (isClick == "2") {
|
||||
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
|
||||
}
|
||||
if (!it.buttons[1].action.isNullOrEmpty()) {
|
||||
dealSchema(it.buttons[1].action, mContext)
|
||||
}
|
||||
}
|
||||
VOICE_ACTION_PUSH_CANCEL -> {
|
||||
AnalyticsUtils.track(
|
||||
Config.NEWS_CARD_SWIPE,
|
||||
"trigger_type",
|
||||
isClick
|
||||
)
|
||||
if (isClick == "2") {
|
||||
mVoiceClient.speakTTSVoice("好的", voiceCmdCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pushBean: PushBean? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (value == null) {
|
||||
floatView?.hide()
|
||||
floatView = null
|
||||
return
|
||||
}
|
||||
field?.showTimeoutShadow = field?.showTimeout?:0
|
||||
Log.d("yilz", "pushbean = $value")
|
||||
if (value != null && value!!.imageUrl == null) {
|
||||
value!!.imageUrl = ""
|
||||
}
|
||||
if (floatView == null) {
|
||||
floatView = FloatView(this, mContext)
|
||||
}
|
||||
floatView?.pushBeanChanged(field)
|
||||
registerVoiceCMD()
|
||||
pushRepository.setPushUIShow(field != null)
|
||||
}
|
||||
|
||||
fun pushMessageFinish(needSave: Boolean = false) {
|
||||
pushRepository.iterateNext(needSave)
|
||||
}
|
||||
|
||||
private fun registerVoiceCMD() {
|
||||
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_CANCEL)
|
||||
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_MAIN)
|
||||
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_LEFT)
|
||||
mVoiceClient.unregisterUnWakeupCommand(VOICE_ACTION_PUSH_RIGHT)
|
||||
|
||||
if (pushBean != null && !pushBean!!.cancelVoiceCmd.isNullOrEmpty()) {
|
||||
mVoiceClient.registerUnWakeupCommand(
|
||||
VOICE_ACTION_PUSH_CANCEL,
|
||||
pushBean!!.cancelVoiceCmd!!.toTypedArray(),
|
||||
voiceCmdCallback
|
||||
)
|
||||
} else {
|
||||
mVoiceClient.registerUnWakeupCommand(
|
||||
VOICE_ACTION_PUSH_CANCEL,
|
||||
arrayOf("忽略", "取消", "算了", "不要", "不要了", "不看了", "关闭"),
|
||||
voiceCmdCallback
|
||||
)
|
||||
}
|
||||
|
||||
if (pushBean != null && !pushBean!!.mainVoiceCmd.isNullOrEmpty()) {
|
||||
mVoiceClient.registerUnWakeupCommand(
|
||||
VOICE_ACTION_PUSH_MAIN,
|
||||
pushBean!!.mainVoiceCmd!!.toTypedArray(),
|
||||
voiceCmdCallback
|
||||
)
|
||||
}
|
||||
|
||||
pushBean?.buttons?.forEach {
|
||||
it?.voiceCmd?.apply {
|
||||
mVoiceClient.registerUnWakeupCommand(
|
||||
VOICE_ACTION_PUSH_RIGHT,
|
||||
toTypedArray(),
|
||||
voiceCmdCallback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun speakDefault(voice: String) {
|
||||
mVoiceClient.speakTTSVoice(voice, voiceCmdCallback)
|
||||
}
|
||||
|
||||
fun pauseAnimator(on: Boolean) {
|
||||
floatView?.pauseTimer(on)
|
||||
}
|
||||
|
||||
fun isAddWindow(): Boolean = floatView?.isAddWindow() ?: false
|
||||
|
||||
fun push() {
|
||||
pushBean?.apply {
|
||||
showTimeout = showTimeoutShadow
|
||||
pushRepository.push(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="@dimen/module_push_clear_bg_radius" />
|
||||
<solid android:color="#FF494B66 "/>
|
||||
</shape>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/module_push_content_only_line_space" />
|
||||
<solid android:color=" #222533" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="@dimen/module_push_ui_decrease_timer_corner"/>
|
||||
<solid android:color="#7F000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/module_push_ui_bkg_corner" />
|
||||
<solid android:color="#F23F4057" />
|
||||
</shape>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="false">
|
||||
<shape>
|
||||
<corners android:radius="@dimen/module_push_ui_button_radius" />
|
||||
<gradient android:angle="135" android:endColor="#1F7EFF" android:startColor="#1E57A4" android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<corners android:radius="@dimen/module_push_ui_button_radius" />
|
||||
<gradient android:angle="135" android:endColor="#124C9A" android:startColor="#123463" android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FA36374A"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="@dimen/module_push_button_margin_top" />
|
||||
<solid android:color="#242736" />
|
||||
<!-- <gradient
|
||||
android:angle="-90"
|
||||
android:endColor="#2F3047"
|
||||
android:startColor="#3F4057"
|
||||
android:type="linear" />-->
|
||||
</shape>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="false">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/module_push_ui_button_radius" />
|
||||
<gradient android:angle="135" android:endColor="#616381" android:startColor="#48495E" />
|
||||
</shape>
|
||||
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/module_push_ui_button_radius" />
|
||||
<gradient android:angle="135" android:endColor="#3A3B4D" android:startColor="#2B2C38" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromDegrees="0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="360">
|
||||
<shape
|
||||
android:innerRadius="@dimen/module_push_timer_inner_radius"
|
||||
android:shape="ring"
|
||||
android:thickness="@dimen/module_push_timer_thickness"
|
||||
android:useLevel="false">
|
||||
<gradient
|
||||
android:centerY="0.50"
|
||||
android:endColor="#001F7FFF"
|
||||
android:startColor="#7A8199"
|
||||
android:type="sweep"
|
||||
android:useLevel="false" />
|
||||
</shape>
|
||||
</rotate>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/module_push_ui_height"
|
||||
android:background="@drawable/module_push_item_background">
|
||||
|
||||
<com.mogo.module.push.view.roundimage.RoundedImageView
|
||||
android:id="@+id/module_push_image"
|
||||
android:layout_width="@dimen/module_push_ui_image_width"
|
||||
android:layout_height="@dimen/module_push_ui_image_height"
|
||||
android:layout_marginLeft="@dimen/module_push_ui_image_marLeft"
|
||||
android:background="#333333"
|
||||
android:scaleType="fitXY"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:riv_corner_radius="@dimen/module_push_ui_image_corner" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/module_push_app_icon_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/module_push_ui_app_icon_leftMargin"
|
||||
android:layout_marginTop="@dimen/module_push_ui_app_icon_topMargin"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/module_push_ui_app_icon_size"
|
||||
app:layout_constraintRight_toLeftOf="@+id/module_push_progress_bar_frame"
|
||||
app:layout_constraintLeft_toRightOf="@+id/module_push_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_goneMarginTop="@dimen/module_push_ui_app_icon_goneTopMargin">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_push_app_icon"
|
||||
android:layout_width="@dimen/module_push_ui_app_icon_size"
|
||||
android:layout_height="@dimen/module_push_ui_app_icon_size"
|
||||
android:layout_marginRight="@dimen/module_push_title_margin_start"
|
||||
android:background="@drawable/module_push_ui_ic_message2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical|left"
|
||||
android:maxWidth="@dimen/module_push_title_mix_width"
|
||||
android:singleLine="true"
|
||||
android:maxLength="25"
|
||||
tools:text="标题标题标题标题标题标题标题"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_ui_title_textSize"
|
||||
app:layout_constrainedWidth="true" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/module_push_ui_content_marginTop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="@id/module_push_app_icon_title"
|
||||
app:layout_constraintRight_toRightOf="@id/module_push_progress_bar_frame"
|
||||
app:layout_constraintTop_toBottomOf="@id/module_push_app_icon_title">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:maxLines="3"
|
||||
android:layout_above="@+id/module_push_buttons"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_ui_title_text_size"
|
||||
tools:text="文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内文本内容文本内容文本内" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/module_push_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/module_push_button_height"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginTop="@dimen/module_push_button_margin_top"
|
||||
android:layout_marginBottom="@dimen/module_push_button_margin_bottom"
|
||||
android:gravity="center"
|
||||
android:weightSum="2">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_button_left"
|
||||
android:layout_width="@dimen/module_push_button_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/module_push_left_button"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:enabled="true"
|
||||
android:lines="1"
|
||||
android:maxWidth="@dimen/module_push_button_maxWidth"
|
||||
android:maxLength="6"
|
||||
android:text="查看"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="@dimen/module_push_title_text_size"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_button_right"
|
||||
android:layout_width="@dimen/module_push_button_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/module_push_button_right_marLeft"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/module_push_right_button"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:lines="1"
|
||||
android:maxWidth="@dimen/module_push_button_maxWidth"
|
||||
android:maxLength="6"
|
||||
android:text="忽略"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="@dimen/module_push_title_text_size"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/module_push_progress_bar_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/module_push_progress_bar_frame_marginTop"
|
||||
android:layout_marginEnd="@dimen/module_push_progress_bar_frame_marginEnd"
|
||||
android:background="@drawable/module_push_decrease_timer_bkg"
|
||||
android:paddingLeft="@dimen/module_push_progress_bar_frame_padding"
|
||||
android:paddingRight="@dimen/module_push_progress_bar_frame_padding"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_timer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:textColor="#80FFFFFF"
|
||||
android:textSize="@dimen/module_push_ui_timer_textSize"
|
||||
tools:text="11s" />
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="@dimen/module_push_ui_width_vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/module_push_item_background"
|
||||
android:maxHeight="@dimen/module_push_item_maxHeight_vertical"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/module_push_ui_app_icon_leftMargin_vertical"
|
||||
android:paddingRight="@dimen/module_push_ui_app_icon_leftMargin_vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/module_push_content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/module_push_content_paddingBottom_vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/module_push_app_icon_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/module_push_ui_app_icon_topMargin_vertical"
|
||||
android:layout_marginBottom="@dimen/module_push_image_marginTop_vertical"
|
||||
android:minHeight="@dimen/module_push_ui_app_icon_size">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_push_app_icon"
|
||||
android:layout_width="@dimen/module_push_ui_app_icon_size"
|
||||
android:layout_height="@dimen/module_push_ui_app_icon_size"
|
||||
android:layout_marginRight="@dimen/module_push_title_margin_start"
|
||||
tools:background="@drawable/module_push_ui_ic_message2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@+id/module_push_app_icon"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical|left"
|
||||
android:maxWidth="@dimen/module_push_title_mix_width"
|
||||
android:singleLine="true"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_ui_title_textSize"
|
||||
tools:text="push title" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/module_push_progress_bar_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/module_push_decrease_timer_bkg"
|
||||
android:paddingLeft="@dimen/module_push_progress_bar_frame_padding"
|
||||
android:paddingRight="@dimen/module_push_progress_bar_frame_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_timer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:textColor="#80FFFFFF"
|
||||
android:textSize="@dimen/module_push_ui_timer_textSize"
|
||||
tools:text="11s" />
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<com.mogo.module.push.view.roundimage.RoundedImageView
|
||||
android:id="@+id/module_push_image"
|
||||
android:layout_width="@dimen/module_push_ui_image_width_vertical"
|
||||
android:layout_height="@dimen/module_push_ui_image_height_vertical"
|
||||
android:layout_gravity="center"
|
||||
android:background="#333333"
|
||||
android:layout_marginBottom="@dimen/module_push_ui_content_marginTop_vertical"
|
||||
android:scaleType="fitXY"
|
||||
app:riv_corner_radius="@dimen/module_push_ui_image_corner" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="@dimen/module_push_button_margin_top"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="4"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_ui_title_text_size"
|
||||
tools:text="文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内文本内容文本内" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/module_push_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/module_push_button_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="@dimen/module_push_button_margin_bottom"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_button_left"
|
||||
android:layout_width="@dimen/module_push_button_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/module_push_left_button"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:lines="1"
|
||||
android:maxWidth="@dimen/module_push_button_maxWidth"
|
||||
android:maxLength="6"
|
||||
android:text="查看"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="@dimen/module_push_title_text_size"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_button_right"
|
||||
android:layout_width="@dimen/module_push_button_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/module_push_button_right_marLeft"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/module_push_right_button"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:lines="1"
|
||||
android:maxWidth="@dimen/module_push_button_maxWidth"
|
||||
android:maxLength="6"
|
||||
android:text="忽略"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="@dimen/module_push_title_text_size"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_activity_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/module_push_activity_title_margin_top"
|
||||
android:text="历史消息(2)"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textSize="@dimen/module_push_activity_title_text_size" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_push_activity_close"
|
||||
android:layout_width="@dimen/module_push_margin_start"
|
||||
android:layout_height="@dimen/module_push_margin_start"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="@dimen/module_push_activity_close_margin_top"
|
||||
android:layout_marginEnd="@dimen/module_push_activity_close_margin_end"
|
||||
android:padding="@dimen/module_push_activity_close_padding"
|
||||
android:src="@drawable/module_push_close" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/module_push_activity_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/module_push_activity_recycler_view_margin_top"
|
||||
android:layout_marginEnd="@dimen/module_push_activity_close_margin_end" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_activity_not_data"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="暂无消息"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_activity_not_data_text_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_activity_clear"
|
||||
android:layout_width="@dimen/module_push_content_only_height"
|
||||
android:layout_height="@dimen/module_push_button_height"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_marginBottom="@dimen/module_push_activity_clear_margin_bottom"
|
||||
android:background="@drawable/module_push_activity_clear_bg"
|
||||
android:gravity="center"
|
||||
android:text="清空历史消息"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_title_text_size"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.mogo.module.push.view.SwipeItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="@dimen/module_push_content_only_line_space">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/module_push_message_item_height">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_push_item_click"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/module_push_activity_close_margin_end"
|
||||
android:background="@drawable/module_push_recycler_item_background">
|
||||
|
||||
<com.mogo.service.imageloader.MogoImageView
|
||||
android:id="@+id/module_push_item_app_icon"
|
||||
android:layout_width="@dimen/module_push_message_app_icon_size"
|
||||
android:layout_height="@dimen/module_push_message_app_icon_size"
|
||||
android:layout_marginStart="@dimen/module_push_message_margin_start"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_item_title"
|
||||
android:layout_width="@dimen/module_push_item_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/module_push_image_margin_top"
|
||||
android:layout_marginTop="@dimen/module_push_item_title_margin_top"
|
||||
android:layout_marginBottom="@dimen/module_push_item_title_margin_bottom"
|
||||
android:ellipsize="end"
|
||||
android:gravity="left"
|
||||
android:maxLines="1"
|
||||
android:text="push title"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_title_text_size"
|
||||
app:layout_constraintBottom_toTopOf="@+id/module_push_item_content"
|
||||
app:layout_constraintLeft_toRightOf="@+id/module_push_item_app_icon"
|
||||
app:layout_constraintWidth_max="@dimen/module_push_item_content_width"
|
||||
app:layout_goneMarginBottom="@dimen/module_push_item_title_gone_margin_bottom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_item_content"
|
||||
android:layout_width="@dimen/module_push_item_content_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/module_push_item_content_margin_end"
|
||||
android:layout_marginBottom="@dimen/module_push_item_content_margin_bottom"
|
||||
android:ellipsize="end"
|
||||
android:gravity="left"
|
||||
android:maxLines="1"
|
||||
android:text="发现系统新版本,共140.3M。部分功能优化,建议下载升级。"
|
||||
android:textColor="#80FFFFFF"
|
||||
android:textSize="@dimen/module_push_item_content_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/module_push_item_title" />
|
||||
|
||||
<com.mogo.service.imageloader.MogoImageView
|
||||
android:id="@+id/module_push_item_image"
|
||||
android:layout_width="@dimen/module_push_message_item_image_size"
|
||||
android:layout_height="@dimen/module_push_message_item_image_size"
|
||||
android:layout_marginEnd="@dimen/module_push_message_item_image_margin_end"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:miv_radius="@dimen/module_push_item_image_radius"
|
||||
app:miv_shape="round" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_item_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/module_push_image_margin_top"
|
||||
android:layout_marginEnd="@dimen/module_push_image_margin_top"
|
||||
android:gravity="right"
|
||||
android:maxLines="1"
|
||||
android:text="3:20"
|
||||
android:textColor="#80FFFFFF"
|
||||
android:textSize="@dimen/module_push_massage_time_text_size"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/module_push_button_margin_top">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_push_item_delete"
|
||||
android:layout_width="@dimen/module_push_message_item_height"
|
||||
android:layout_height="@dimen/module_push_message_item_height"
|
||||
android:layout_marginStart="@dimen/module_push_button_margin_top"
|
||||
android:background="@drawable/module_push_recycler_item_background"
|
||||
android:gravity="center"
|
||||
android:text="清除"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_push_title_text_size" />
|
||||
</FrameLayout>
|
||||
</com.mogo.module.push.view.SwipeItemLayout>
|
||||
90
modules/mogo-module-push/src/main/res/values-ldpi/dimens.xml
Normal file
90
modules/mogo-module-push/src/main/res/values-ldpi/dimens.xml
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<dimen name="module_push_margin_top">16px</dimen>
|
||||
<dimen name="module_push_size">352px</dimen>
|
||||
<dimen name="module_push_margin_start">32px</dimen>
|
||||
<dimen name="module_push_app_icon_size">32px</dimen>
|
||||
<dimen name="module_push_app_icon_margin_start">16.5px</dimen>
|
||||
<dimen name="module_push_title_margin_start">12px</dimen>
|
||||
<dimen name="module_push_title_margin_top">20px</dimen>
|
||||
<dimen name="module_push_title_text_size">18px</dimen>
|
||||
<dimen name="module_push_title_mix_width">210px</dimen>
|
||||
<dimen name="module_push_timer_margin_end">13px</dimen>
|
||||
<dimen name="module_push_timer_text_size">15px</dimen>
|
||||
<dimen name="module_push_timer_margin_top">18px</dimen>
|
||||
<dimen name="module_push_image_width">320px</dimen>
|
||||
<dimen name="module_push_image_height">180px</dimen>
|
||||
<dimen name="module_push_image_margin_top">21px</dimen>
|
||||
<dimen name="module_push_content_only_width">320px</dimen>
|
||||
<dimen name="module_push_content_only_height">160px</dimen>
|
||||
<dimen name="module_push_content_only_line_space">9px</dimen>
|
||||
<dimen name="module_push_content_only_padding">20px</dimen>
|
||||
<dimen name="module_push_button_width">0px</dimen>
|
||||
<dimen name="module_push_button_height">48px</dimen>
|
||||
<dimen name="module_push_button_margin_top">10px</dimen>
|
||||
<dimen name="module_push_button_margin_bottom">14px</dimen>
|
||||
<dimen name="module_push_activity_title_margin_top">35px</dimen>
|
||||
<dimen name="module_push_activity_title_text_size">20px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_top">29px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_end">90px</dimen>
|
||||
<dimen name="module_push_activity_close_padding">5px</dimen>
|
||||
<dimen name="module_push_activity_recycler_view_margin_top">92px</dimen>
|
||||
<dimen name="module_push_activity_not_data_text_size">38px</dimen>
|
||||
<dimen name="module_push_activity_clear_margin_bottom">36px</dimen>
|
||||
<dimen name="module_push_message_item_height">106px</dimen>
|
||||
<dimen name="module_push_message_app_icon_size">64px</dimen>
|
||||
<dimen name="module_push_message_margin_start">24px</dimen>
|
||||
<dimen name="module_push_item_title_margin_top">25px</dimen>
|
||||
<dimen name="module_push_item_title_gone_margin_bottom">44px</dimen>
|
||||
<dimen name="module_push_item_title_margin_bottom">2px</dimen>
|
||||
<dimen name="module_push_item_content_margin_end">20px</dimen>
|
||||
<dimen name="module_push_item_content_margin_bottom">27px</dimen>
|
||||
<dimen name="module_push_item_content_text_size">16px</dimen>
|
||||
<dimen name="module_push_message_item_image_size">64px</dimen>
|
||||
<dimen name="module_push_message_item_image_margin_end">100px</dimen>
|
||||
<dimen name="module_push_massage_time_text_size">14px</dimen>
|
||||
|
||||
<dimen name="module_push_image_margin_bottom">22px</dimen>
|
||||
<dimen name="module_push_button_radius">27px</dimen>
|
||||
<dimen name="module_push_timer_inner_radius">14px</dimen>
|
||||
<dimen name="module_push_timer_thickness">1.5px</dimen>
|
||||
<dimen name="module_push_clear_bg_radius">24px</dimen>
|
||||
<dimen name="module_push_image_radius">10px</dimen>
|
||||
<dimen name="module_push_item_image_radius">4px</dimen>
|
||||
<dimen name="module_push_item_content_width">560px</dimen>
|
||||
<dimen name="module_push_ui_height">194px</dimen>
|
||||
<dimen name="module_push_ui_image_width">266px</dimen>
|
||||
<dimen name="module_push_ui_image_height">178px</dimen>
|
||||
<dimen name="module_push_ui_image_marLeft">8px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin">12px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin">16px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_goneTopMargin">19px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_size">30px</dimen>
|
||||
<dimen name="module_push_ui_title_textSize">16px</dimen>
|
||||
<dimen name="module_push_ui_decrease_timer_corner">8px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginTop">17px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginEnd">19px</dimen>
|
||||
<dimen name="module_push_ui_timer_textSize">16px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_padding">11px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop">6px</dimen>
|
||||
<dimen name="module_push_ui_title_text_size">18px</dimen>
|
||||
<dimen name="module_push_ui_button_radius">10px</dimen>
|
||||
<dimen name="module_push_ui_bkg_corner">17px</dimen>
|
||||
<dimen name="module_push_button_right_marLeft">10px</dimen>
|
||||
<dimen name="module_push_ui_image_corner">8px</dimen>
|
||||
<dimen name="module_push_button_maxWidth">242px</dimen>
|
||||
<dimen name="module_push_ui_height_vertical">270px</dimen>
|
||||
<dimen name="module_push_ui_width_vertical">374px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin_vertical">24px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin_vertical">19px</dimen>
|
||||
<dimen name="module_push_image_marginTop_vertical">8px</dimen>
|
||||
<dimen name="module_push_ui_image_width_vertical">328px</dimen>
|
||||
<dimen name="module_push_ui_image_height_vertical">164px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop_vertical">15px</dimen>
|
||||
<dimen name="module_push_image_qr_size_vertical">150px</dimen>
|
||||
<dimen name="module_push_window_x">20px</dimen>
|
||||
<dimen name="module_push_window_y">0px</dimen>
|
||||
<dimen name="module_push_item_minHeight_vertical">310px</dimen>
|
||||
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
|
||||
<dimen name="module_push_content_paddingBottom_vertical">60px</dimen>
|
||||
</resources>
|
||||
90
modules/mogo-module-push/src/main/res/values-mdpi/dimens.xml
Normal file
90
modules/mogo-module-push/src/main/res/values-mdpi/dimens.xml
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<dimen name="module_push_margin_top">16px</dimen>
|
||||
<dimen name="module_push_size">352px</dimen>
|
||||
<dimen name="module_push_margin_start">32px</dimen>
|
||||
<dimen name="module_push_app_icon_size">32px</dimen>
|
||||
<dimen name="module_push_app_icon_margin_start">16.5px</dimen>
|
||||
<dimen name="module_push_title_margin_start">12px</dimen>
|
||||
<dimen name="module_push_title_margin_top">20px</dimen>
|
||||
<dimen name="module_push_title_text_size">18px</dimen>
|
||||
<dimen name="module_push_title_mix_width">210px</dimen>
|
||||
<dimen name="module_push_timer_margin_end">13px</dimen>
|
||||
<dimen name="module_push_timer_text_size">15px</dimen>
|
||||
<dimen name="module_push_timer_margin_top">18px</dimen>
|
||||
<dimen name="module_push_image_width">320px</dimen>
|
||||
<dimen name="module_push_image_height">180px</dimen>
|
||||
<dimen name="module_push_image_margin_top">21px</dimen>
|
||||
<dimen name="module_push_content_only_width">320px</dimen>
|
||||
<dimen name="module_push_content_only_height">160px</dimen>
|
||||
<dimen name="module_push_content_only_line_space">9px</dimen>
|
||||
<dimen name="module_push_content_only_padding">20px</dimen>
|
||||
<dimen name="module_push_button_width">0px</dimen>
|
||||
<dimen name="module_push_button_height">48px</dimen>
|
||||
<dimen name="module_push_button_margin_top">10px</dimen>
|
||||
<dimen name="module_push_button_margin_bottom">14px</dimen>
|
||||
<dimen name="module_push_activity_title_margin_top">35px</dimen>
|
||||
<dimen name="module_push_activity_title_text_size">20px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_top">29px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_end">90px</dimen>
|
||||
<dimen name="module_push_activity_close_padding">5px</dimen>
|
||||
<dimen name="module_push_activity_recycler_view_margin_top">92px</dimen>
|
||||
<dimen name="module_push_activity_not_data_text_size">38px</dimen>
|
||||
<dimen name="module_push_activity_clear_margin_bottom">36px</dimen>
|
||||
<dimen name="module_push_message_item_height">106px</dimen>
|
||||
<dimen name="module_push_message_app_icon_size">64px</dimen>
|
||||
<dimen name="module_push_message_margin_start">24px</dimen>
|
||||
<dimen name="module_push_item_title_margin_top">25px</dimen>
|
||||
<dimen name="module_push_item_title_gone_margin_bottom">44px</dimen>
|
||||
<dimen name="module_push_item_title_margin_bottom">2px</dimen>
|
||||
<dimen name="module_push_item_content_margin_end">20px</dimen>
|
||||
<dimen name="module_push_item_content_margin_bottom">27px</dimen>
|
||||
<dimen name="module_push_item_content_text_size">16px</dimen>
|
||||
<dimen name="module_push_message_item_image_size">64px</dimen>
|
||||
<dimen name="module_push_message_item_image_margin_end">100px</dimen>
|
||||
<dimen name="module_push_massage_time_text_size">14px</dimen>
|
||||
|
||||
<dimen name="module_push_image_margin_bottom">22px</dimen>
|
||||
<dimen name="module_push_button_radius">27px</dimen>
|
||||
<dimen name="module_push_timer_inner_radius">14px</dimen>
|
||||
<dimen name="module_push_timer_thickness">1.5px</dimen>
|
||||
<dimen name="module_push_clear_bg_radius">24px</dimen>
|
||||
<dimen name="module_push_image_radius">10px</dimen>
|
||||
<dimen name="module_push_item_image_radius">4px</dimen>
|
||||
<dimen name="module_push_item_content_width">560px</dimen>
|
||||
<dimen name="module_push_ui_height">194px</dimen>
|
||||
<dimen name="module_push_ui_image_width">266px</dimen>
|
||||
<dimen name="module_push_ui_image_height">178px</dimen>
|
||||
<dimen name="module_push_ui_image_marLeft">8px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin">12px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin">16px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_goneTopMargin">19px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_size">30px</dimen>
|
||||
<dimen name="module_push_ui_title_textSize">16px</dimen>
|
||||
<dimen name="module_push_ui_decrease_timer_corner">8px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginTop">17px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginEnd">19px</dimen>
|
||||
<dimen name="module_push_ui_timer_textSize">16px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_padding">11px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop">6px</dimen>
|
||||
<dimen name="module_push_ui_title_text_size">18px</dimen>
|
||||
<dimen name="module_push_ui_button_radius">10px</dimen>
|
||||
<dimen name="module_push_ui_bkg_corner">17px</dimen>
|
||||
<dimen name="module_push_button_right_marLeft">10px</dimen>
|
||||
<dimen name="module_push_ui_image_corner">8px</dimen>
|
||||
<dimen name="module_push_button_maxWidth">242px</dimen>
|
||||
<dimen name="module_push_ui_height_vertical">270px</dimen>
|
||||
<dimen name="module_push_ui_width_vertical">374px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin_vertical">24px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin_vertical">19px</dimen>
|
||||
<dimen name="module_push_image_marginTop_vertical">8px</dimen>
|
||||
<dimen name="module_push_ui_image_width_vertical">328px</dimen>
|
||||
<dimen name="module_push_ui_image_height_vertical">164px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop_vertical">15px</dimen>
|
||||
<dimen name="module_push_image_qr_size_vertical">150px</dimen>
|
||||
<dimen name="module_push_window_x">20px</dimen>
|
||||
<dimen name="module_push_window_y">0px</dimen>
|
||||
<dimen name="module_push_item_minHeight_vertical">310px</dimen>
|
||||
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
|
||||
<dimen name="module_push_content_paddingBottom_vertical">60px</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<dimen name="module_push_margin_top">30px</dimen>
|
||||
<dimen name="module_push_size">660px</dimen>
|
||||
<dimen name="module_push_margin_start">60px</dimen>
|
||||
<dimen name="module_push_app_icon_size">60px</dimen>
|
||||
<dimen name="module_push_app_icon_margin_start">31px</dimen>
|
||||
<dimen name="module_push_title_margin_start">22px</dimen>
|
||||
<dimen name="module_push_title_margin_top">38px</dimen>
|
||||
<dimen name="module_push_title_text_size">32px</dimen>
|
||||
<dimen name="module_push_title_mix_width">394px</dimen>
|
||||
<dimen name="module_push_timer_margin_end">24px</dimen>
|
||||
<dimen name="module_push_timer_text_size">28px</dimen>
|
||||
<dimen name="module_push_timer_margin_top">34px</dimen>
|
||||
<dimen name="module_push_image_width">600px</dimen>
|
||||
<dimen name="module_push_image_height">338px</dimen>
|
||||
<dimen name="module_push_image_margin_top">40px</dimen>
|
||||
<dimen name="module_push_content_only_width">605px</dimen>
|
||||
<dimen name="module_push_content_only_height">300px</dimen>
|
||||
<dimen name="module_push_content_only_line_space">16px</dimen>
|
||||
<dimen name="module_push_content_only_padding">53px</dimen>
|
||||
<dimen name="module_push_button_width">0px</dimen>
|
||||
<dimen name="module_push_button_height">90px</dimen>
|
||||
<dimen name="module_push_button_margin_top">20px</dimen>
|
||||
<dimen name="module_push_button_margin_bottom">26px</dimen>
|
||||
<dimen name="module_push_activity_title_margin_top">66px</dimen>
|
||||
<dimen name="module_push_activity_title_text_size">38px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_top">54px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_end">160px</dimen>
|
||||
<dimen name="module_push_activity_close_padding">10px</dimen>
|
||||
<dimen name="module_push_activity_recycler_view_margin_top">173px</dimen>
|
||||
<dimen name="module_push_activity_not_data_text_size">72px</dimen>
|
||||
<dimen name="module_push_activity_clear_margin_bottom">68px</dimen>
|
||||
<dimen name="module_push_message_item_height">200px</dimen>
|
||||
<dimen name="module_push_message_app_icon_size">120px</dimen>
|
||||
<dimen name="module_push_message_margin_start">50px</dimen>
|
||||
<dimen name="module_push_item_title_margin_top">43px</dimen>
|
||||
<dimen name="module_push_item_title_margin_bottom">6px</dimen>
|
||||
<dimen name="module_push_item_title_gone_margin_bottom">84px</dimen>
|
||||
|
||||
<dimen name="module_push_item_content_margin_end">40px</dimen>
|
||||
<dimen name="module_push_item_content_margin_bottom">52px</dimen>
|
||||
<dimen name="module_push_item_content_text_size">30px</dimen>
|
||||
<dimen name="module_push_message_item_image_size">120px</dimen>
|
||||
<dimen name="module_push_message_item_image_margin_end">180px</dimen>
|
||||
<dimen name="module_push_massage_time_text_size">34px</dimen>
|
||||
<dimen name="module_push_image_margin_bottom">42px</dimen>
|
||||
<dimen name="module_push_button_radius">51px</dimen>
|
||||
<dimen name="module_push_timer_inner_radius">27px</dimen>
|
||||
<dimen name="module_push_timer_thickness">3px</dimen>
|
||||
<dimen name="module_push_clear_bg_radius">45px</dimen>
|
||||
<dimen name="module_push_image_radius">20px</dimen>
|
||||
<dimen name="module_push_item_image_radius">8px</dimen>
|
||||
<dimen name="module_push_item_content_width">1000px</dimen>
|
||||
<dimen name="module_push_ui_height">350px</dimen>
|
||||
<dimen name="module_push_ui_image_width">480px</dimen>
|
||||
<dimen name="module_push_ui_image_height">320px</dimen>
|
||||
<dimen name="module_push_ui_image_marLeft">17px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin">21px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin">30px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_goneTopMargin">33px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_size">50px</dimen>
|
||||
<dimen name="module_push_ui_title_textSize">30px</dimen>
|
||||
<dimen name="module_push_ui_decrease_timer_corner">14px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginTop">30px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginEnd">27px</dimen>
|
||||
<dimen name="module_push_ui_timer_textSize">30px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_padding">21px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop">11px</dimen>
|
||||
<dimen name="module_push_ui_title_text_size">34px</dimen>
|
||||
<dimen name="module_push_ui_button_radius">20px</dimen>
|
||||
<dimen name="module_push_ui_bkg_corner">30px</dimen>
|
||||
<dimen name="module_push_button_right_marLeft">20px</dimen>
|
||||
<dimen name="module_push_ui_image_corner">15px</dimen>
|
||||
<dimen name="module_push_button_maxWidth">242px</dimen>
|
||||
<dimen name="module_push_ui_height_vertical">486px</dimen>
|
||||
<dimen name="module_push_ui_width_vertical">700px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin_vertical">30px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin_vertical">33px</dimen>
|
||||
<dimen name="module_push_image_marginTop_vertical">16px</dimen>
|
||||
<dimen name="module_push_ui_image_width_vertical">640px</dimen>
|
||||
<dimen name="module_push_ui_image_height_vertical">296px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop_vertical">30px</dimen>
|
||||
<dimen name="module_push_image_qr_size_vertical">250px</dimen>
|
||||
<dimen name="module_push_window_x">20px</dimen>
|
||||
<dimen name="module_push_window_y">0px</dimen>
|
||||
<dimen name="module_push_item_minHeight_vertical">618px</dimen>
|
||||
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
|
||||
<dimen name="module_push_content_paddingBottom_vertical">120px</dimen>
|
||||
</resources>
|
||||
30
modules/mogo-module-push/src/main/res/values/attr.xml
Normal file
30
modules/mogo-module-push/src/main/res/values/attr.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="RoundedImageView">
|
||||
<attr name="riv_corner_radius" format="dimension" />
|
||||
<attr name="riv_corner_radius_top_left" format="dimension" />
|
||||
<attr name="riv_corner_radius_top_right" format="dimension" />
|
||||
<attr name="riv_corner_radius_bottom_left" format="dimension" />
|
||||
<attr name="riv_corner_radius_bottom_right" format="dimension" />
|
||||
<attr name="riv_border_width" format="dimension" />
|
||||
<attr name="riv_border_color" format="color" />
|
||||
<attr name="riv_mutate_background" format="boolean" />
|
||||
<attr name="riv_oval" format="boolean" />
|
||||
<attr name="android:scaleType" />
|
||||
<attr name="riv_tile_Mode">
|
||||
<enum name="clamp" value="0" />
|
||||
<enum name="repeat" value="1" />
|
||||
<enum name="mirror" value="2" />
|
||||
</attr>
|
||||
<attr name="riv_tile_Mode_x">
|
||||
<enum name="clamp" value="0" />
|
||||
<enum name="repeat" value="1" />
|
||||
<enum name="mirror" value="2" />
|
||||
</attr>
|
||||
<attr name="riv_tile_Mode_y">
|
||||
<enum name="clamp" value="0" />
|
||||
<enum name="repeat" value="1" />
|
||||
<enum name="mirror" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
90
modules/mogo-module-push/src/main/res/values/dimens.xml
Normal file
90
modules/mogo-module-push/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<dimen name="module_push_margin_top">16px</dimen>
|
||||
<dimen name="module_push_size">352px</dimen>
|
||||
<dimen name="module_push_margin_start">32px</dimen>
|
||||
<dimen name="module_push_app_icon_size">32px</dimen>
|
||||
<dimen name="module_push_app_icon_margin_start">16.5px</dimen>
|
||||
<dimen name="module_push_title_margin_start">12px</dimen>
|
||||
<dimen name="module_push_title_margin_top">20px</dimen>
|
||||
<dimen name="module_push_title_text_size">18px</dimen>
|
||||
<dimen name="module_push_title_mix_width">210px</dimen>
|
||||
<dimen name="module_push_timer_margin_end">13px</dimen>
|
||||
<dimen name="module_push_timer_text_size">15px</dimen>
|
||||
<dimen name="module_push_timer_margin_top">18px</dimen>
|
||||
<dimen name="module_push_image_width">320px</dimen>
|
||||
<dimen name="module_push_image_height">180px</dimen>
|
||||
<dimen name="module_push_image_margin_top">21px</dimen>
|
||||
<dimen name="module_push_content_only_width">320px</dimen>
|
||||
<dimen name="module_push_content_only_height">160px</dimen>
|
||||
<dimen name="module_push_content_only_line_space">9px</dimen>
|
||||
<dimen name="module_push_content_only_padding">20px</dimen>
|
||||
<dimen name="module_push_button_width">0px</dimen>
|
||||
<dimen name="module_push_button_height">48px</dimen>
|
||||
<dimen name="module_push_button_margin_top">10px</dimen>
|
||||
<dimen name="module_push_button_margin_bottom">14px</dimen>
|
||||
<dimen name="module_push_activity_title_margin_top">35px</dimen>
|
||||
<dimen name="module_push_activity_title_text_size">20px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_top">29px</dimen>
|
||||
<dimen name="module_push_activity_close_margin_end">90px</dimen>
|
||||
<dimen name="module_push_activity_close_padding">5px</dimen>
|
||||
<dimen name="module_push_activity_recycler_view_margin_top">92px</dimen>
|
||||
<dimen name="module_push_activity_not_data_text_size">38px</dimen>
|
||||
<dimen name="module_push_activity_clear_margin_bottom">36px</dimen>
|
||||
<dimen name="module_push_message_item_height">106px</dimen>
|
||||
<dimen name="module_push_message_app_icon_size">64px</dimen>
|
||||
<dimen name="module_push_message_margin_start">24px</dimen>
|
||||
<dimen name="module_push_item_title_margin_top">25px</dimen>
|
||||
<dimen name="module_push_item_title_gone_margin_bottom">44px</dimen>
|
||||
<dimen name="module_push_item_title_margin_bottom">2px</dimen>
|
||||
<dimen name="module_push_item_content_margin_end">20px</dimen>
|
||||
<dimen name="module_push_item_content_margin_bottom">27px</dimen>
|
||||
<dimen name="module_push_item_content_text_size">16px</dimen>
|
||||
<dimen name="module_push_message_item_image_size">64px</dimen>
|
||||
<dimen name="module_push_message_item_image_margin_end">100px</dimen>
|
||||
<dimen name="module_push_massage_time_text_size">14px</dimen>
|
||||
|
||||
<dimen name="module_push_image_margin_bottom">22px</dimen>
|
||||
<dimen name="module_push_button_radius">27px</dimen>
|
||||
<dimen name="module_push_timer_inner_radius">14px</dimen>
|
||||
<dimen name="module_push_timer_thickness">1.5px</dimen>
|
||||
<dimen name="module_push_clear_bg_radius">24px</dimen>
|
||||
<dimen name="module_push_image_radius">10px</dimen>
|
||||
<dimen name="module_push_item_image_radius">4px</dimen>
|
||||
<dimen name="module_push_item_content_width">560px</dimen>
|
||||
<dimen name="module_push_ui_height">194px</dimen>
|
||||
<dimen name="module_push_ui_image_width">266px</dimen>
|
||||
<dimen name="module_push_ui_image_height">178px</dimen>
|
||||
<dimen name="module_push_ui_image_marLeft">8px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin">12px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin">16px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_goneTopMargin">19px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_size">30px</dimen>
|
||||
<dimen name="module_push_ui_title_textSize">16px</dimen>
|
||||
<dimen name="module_push_ui_decrease_timer_corner">8px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginTop">17px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_marginEnd">19px</dimen>
|
||||
<dimen name="module_push_ui_timer_textSize">16px</dimen>
|
||||
<dimen name="module_push_progress_bar_frame_padding">11px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop">6px</dimen>
|
||||
<dimen name="module_push_ui_title_text_size">18px</dimen>
|
||||
<dimen name="module_push_ui_button_radius">10px</dimen>
|
||||
<dimen name="module_push_ui_bkg_corner">17px</dimen>
|
||||
<dimen name="module_push_button_right_marLeft">10px</dimen>
|
||||
<dimen name="module_push_ui_image_corner">8px</dimen>
|
||||
<dimen name="module_push_button_maxWidth">242px</dimen>
|
||||
<dimen name="module_push_ui_height_vertical">270px</dimen>
|
||||
<dimen name="module_push_ui_width_vertical">374px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_leftMargin_vertical">24px</dimen>
|
||||
<dimen name="module_push_ui_app_icon_topMargin_vertical">19px</dimen>
|
||||
<dimen name="module_push_image_marginTop_vertical">8px</dimen>
|
||||
<dimen name="module_push_ui_image_width_vertical">328px</dimen>
|
||||
<dimen name="module_push_ui_image_height_vertical">164px</dimen>
|
||||
<dimen name="module_push_ui_content_marginTop_vertical">15px</dimen>
|
||||
<dimen name="module_push_image_qr_size_vertical">150px</dimen>
|
||||
<dimen name="module_push_window_x">20px</dimen>
|
||||
<dimen name="module_push_window_y">0px</dimen>
|
||||
<dimen name="module_push_item_minHeight_vertical">310px</dimen>
|
||||
<dimen name="module_push_item_maxHeight_vertical">350px</dimen>
|
||||
<dimen name="module_push_content_paddingBottom_vertical">60px</dimen>
|
||||
</resources>
|
||||
3
modules/mogo-module-push/src/main/res/values/strings.xml
Normal file
3
modules/mogo-module-push/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">mogo-module-push</string>
|
||||
</resources>
|
||||
9
modules/mogo-module-push/src/main/res/values/styles.xml
Normal file
9
modules/mogo-module-push/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
|
||||
<style name="ModulePushMessageTheme" parent="Theme.AppCompat.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
|
||||
<item name="android:windowBackground">@drawable/module_push_message_activity_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -22,12 +22,10 @@ include ':modules:mogo-module-share'
|
||||
include ':modules:mogo-module-service'
|
||||
include ':modules:mogo-module-back'
|
||||
include ':modules:mogo-module-authorize'
|
||||
//include ':modules:mogo-module-guide'
|
||||
include ':libraries:map-amap'
|
||||
include ':libraries:mogo-map-api'
|
||||
include ':modules:mogo-module-apps'
|
||||
include ':modules:mogo-module-extensions'
|
||||
//include ':foudations:mogo-connection'
|
||||
include ':modules:mogo-module-gps-simulator'
|
||||
include ':modules:mogo-module-gps-simulator-debug'
|
||||
include ':modules:mogo-module-gps-simulator-noop'
|
||||
@@ -36,3 +34,7 @@ include ':modules:mogo-module-media'
|
||||
include ':modules:mogo-module-v2x'
|
||||
include ':main-extensions:mogo-module-main-independent'
|
||||
include ':main-extensions:mogo-module-main-launcher'
|
||||
|
||||
include ':modules:mogo-module-push'
|
||||
include ':modules:mogo-module-push-base'
|
||||
include ':modules:mogo-module-push-noop'
|
||||
|
||||
@@ -64,4 +64,10 @@ if [ $? -ne 0 ]; then exit; fi
|
||||
if [ $? -ne 0 ]; then exit; fi
|
||||
./gradlew :foudations:mogo-base-services-apk:clean :foudations:mogo-base-services-apk:uploadArchives
|
||||
if [ $? -ne 0 ]; then exit; fi
|
||||
./gradlew :foudations:mogo-base-services-sdk:clean :foudations:mogo-base-services-sdk:uploadArchives
|
||||
./gradlew :foudations:mogo-base-services-sdk:clean :foudations:mogo-base-services-sdk:uploadArchives
|
||||
if [ $? -ne 0 ]; then exit; fi
|
||||
./gradlew :modules:mogo-module-push-base:clean :modules:mogo-module-push-base:uploadArchives
|
||||
if [ $? -ne 0 ]; then exit; fi
|
||||
./gradlew :modules:mogo-module-push-noop:clean :modules:mogo-module-push-noop:uploadArchives
|
||||
if [ $? -ne 0 ]; then exit; fi
|
||||
./gradlew :modules:mogo-module-push:clean :modules:mogo-module-push:uploadArchives
|
||||
Reference in New Issue
Block a user