feat : library
This commit is contained in:
3
library/.gitignore
vendored
Normal file
3
library/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/build
|
||||
*.iml
|
||||
.DS_Sotre
|
134
library/build.gradle
Normal file
134
library/build.gradle
Normal file
@@ -0,0 +1,134 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
|
||||
defaultConfig {
|
||||
minSdkVersion MIN_SDK_VERSION.toInteger()
|
||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
viewBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
||||
main {
|
||||
java.srcDirs = [
|
||||
'src/main/java',
|
||||
'src/module_easypermission/java',
|
||||
'src/module_luban/java',
|
||||
'src/module_common/java',
|
||||
|
||||
]
|
||||
|
||||
res.srcDirs = [
|
||||
'src/main/res',
|
||||
'src/module_easypermission/res',
|
||||
'src/module_common/res',
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
buildToolsVersion = '30.0.3'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def SmartRefreshLayoutVersion = "1.0.3"
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
api 'androidx.cardview:cardview:1.0.0'
|
||||
api 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
api "androidx.fragment:fragment:1.6.1"
|
||||
api "androidx.fragment:fragment-ktx:1.6.1"
|
||||
|
||||
api "com.squareup.retrofit2:retrofit:2.9.0"
|
||||
api "com.squareup.okhttp3:okhttp:4.10.0"
|
||||
api "com.squareup.okhttp3:logging-interceptor:4.10.0"
|
||||
api "com.squareup.okio:okio:3.0.0"
|
||||
|
||||
api "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
|
||||
api 'com.google.code.gson:gson:2.9.0'
|
||||
|
||||
// api "com.scwang.smartrefresh:SmartRefreshLayout:${SmartRefreshLayoutVersion}"
|
||||
// api "com.scwang.smartrefresh:SmartRefreshHeader:${SmartRefreshLayoutVersion}"
|
||||
|
||||
|
||||
api "io.github.scwang90:refresh-layout-kernel:3.0.0-alpha" //核心必须依赖
|
||||
// api "io.github.scwang90:refresh-header-classics:3.0.0-alpha" //经典刷新头
|
||||
api "io.github.scwang90:refresh-header-material:3.0.0-alpha" //谷歌刷新头
|
||||
api "io.github.scwang90:refresh-footer-classics:3.0.0-alpha" //经典加载
|
||||
|
||||
api "io.reactivex.rxjava2:rxjava:2.2.12"
|
||||
api "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
api "com.trello.rxlifecycle3:rxlifecycle:3.1.0"
|
||||
api "com.trello.rxlifecycle3:rxlifecycle-android:3.1.0"
|
||||
api "com.trello.rxlifecycle3:rxlifecycle-components:3.1.0"
|
||||
api "com.github.bumptech.glide:glide:4.13.2"
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:4.13.2"
|
||||
|
||||
api "com.orhanobut:logger:2.2.0"
|
||||
|
||||
api "org.greenrobot:eventbus:3.3.1"
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
api 'com.github.getActivity:ToastUtils:10.5'
|
||||
|
||||
api 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
api 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||
api 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
|
||||
//mmkv
|
||||
api 'com.tencent:mmkv:1.2.13'
|
||||
|
||||
api "jp.wasabeef:glide-transformations:3.0.1"
|
||||
|
||||
//流式布局
|
||||
api 'com.google.android.flexbox:flexbox:3.0.0'
|
||||
|
||||
api 'io.github.razerdp:BasePopup:3.2.1'
|
||||
|
||||
// api 'com.umeng.sdk:utdid:1.1.5.3'
|
||||
|
||||
// api 'com.facebook.android:facebook-android-sdk:16.2.0'
|
||||
// api 'com.facebook.android:facebook-login:16.2.0'
|
||||
|
||||
// 网络请求chrome数据调试
|
||||
// api 'com.facebook.stetho:stetho:1.5.1'
|
||||
// api 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
|
||||
api project(':libs:lib_utils')
|
||||
api project(':libs:lib_core')
|
||||
api project(':libs:lib_encipher')
|
||||
|
||||
api 'com.qcloud.cos:cos-android:5.9.25'
|
||||
// api 'com.liulishuo.filedownloader:library:1.7.7'
|
||||
|
||||
// api "com.github.yyued:SVGAPlayer-Android:2.6.1"
|
||||
// api "com.github.yyued:SVGAPlayer-Android:latest"
|
||||
implementation 'com.squareup.wire:wire-runtime:4.4.1'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
21
library/proguard-rules.pro
vendored
Normal file
21
library/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
|
30
library/src/main/AndroidManifest.xml
Normal file
30
library/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.chwl.library"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
tools:node="remove"
|
||||
android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission
|
||||
tools:node="remove"
|
||||
android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".easypermisssion.AppSettingsDialogHolderActivity"
|
||||
android:exported="false"
|
||||
android:label=""
|
||||
android:theme="@style/EasyPermissions.Transparent" />
|
||||
</application>
|
||||
</manifest>
|
@@ -0,0 +1,226 @@
|
||||
package com.chwl.library.adapters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Created by MadisonRong on 15/7/30.
|
||||
*/
|
||||
public abstract class BaseListRecyclerViewAdapter<T, VH extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<VH> implements List<T> {
|
||||
private final Object lock = new Object();
|
||||
private final List<T> list;
|
||||
|
||||
public BaseListRecyclerViewAdapter() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
public BaseListRecyclerViewAdapter(int capacity){
|
||||
list = new ArrayList<T>(capacity);
|
||||
}
|
||||
|
||||
public BaseListRecyclerViewAdapter(Collection<? extends T> collection) {
|
||||
this.list = new ArrayList<T>(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int location, T object) {
|
||||
synchronized (lock) {
|
||||
list.add(location, object);
|
||||
notifyItemInserted(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T object) {
|
||||
synchronized (lock) {
|
||||
if (list.add(object)) {
|
||||
int position = list.indexOf(object);
|
||||
notifyItemInserted(position);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> collection) {
|
||||
synchronized (lock) {
|
||||
int lastIndex = list.size();
|
||||
if (list.addAll(collection)) {
|
||||
notifyItemRangeInserted(lastIndex, collection.size());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int location, Collection<? extends T> collection) {
|
||||
synchronized (lock) {
|
||||
if (list.addAll(location, collection)) {
|
||||
notifyItemRangeInserted(location, collection.size());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (lock) {
|
||||
int size = list.size();
|
||||
list.clear();
|
||||
notifyItemRangeRemoved(0, size);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object object) {
|
||||
return list.contains(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
return list.contains(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int location) {
|
||||
return list.get(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object object) {
|
||||
return list.indexOf(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object object) {
|
||||
return list.lastIndexOf(object);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return list.listIterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int location) {
|
||||
return list.listIterator(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int location) {
|
||||
synchronized (lock) {
|
||||
T item = list.remove(location);
|
||||
notifyItemRemoved(list.indexOf(item));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object object) {
|
||||
boolean modified = false;
|
||||
synchronized (lock) {
|
||||
if (list.contains(object)) {
|
||||
int position = list.indexOf(object);
|
||||
list.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
boolean modified = false;
|
||||
synchronized (lock) {
|
||||
Iterator iterator = collection.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Object object = iterator.next();
|
||||
if (list.contains(object)){
|
||||
int position = list.indexOf(object);
|
||||
list.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
boolean modified = false;
|
||||
synchronized (lock) {
|
||||
modified = list.retainAll(collection);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int location, T object) {
|
||||
synchronized (lock) {
|
||||
T item = list.set(location, object);
|
||||
notifyItemInserted(location);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<T> subList(int start, int end) {
|
||||
return list.subList(start, end);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T1> T1[] toArray(T1[] array) {
|
||||
return list.toArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof List && list.equals(o);
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2015 tyrantgit
|
||||
*
|
||||
* 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.chwl.library.animator;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Path;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.chwl.library.R;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
public abstract class AbstractPathAnimator {
|
||||
private final Random mRandom;
|
||||
protected final Config mConfig;
|
||||
|
||||
|
||||
public AbstractPathAnimator(Config config) {
|
||||
mConfig = config;
|
||||
mRandom = new Random();
|
||||
}
|
||||
|
||||
public float randomRotation() {
|
||||
return mRandom.nextFloat() * 28.6F - 14.3F;
|
||||
}
|
||||
|
||||
public Path createPath(AtomicInteger counter, View view, int factor) {
|
||||
Random r = mRandom;
|
||||
int x = r.nextInt(mConfig.xRand);
|
||||
int x2 = r.nextInt(mConfig.xRand);
|
||||
int y = mConfig.initY;
|
||||
int y2 = counter.intValue() * 15 + mConfig.animLength * factor + r.nextInt(mConfig.animLengthRand);
|
||||
factor = y2 / mConfig.bezierFactor;
|
||||
x = mConfig.xPointFactor + x;
|
||||
x2 = mConfig.xPointFactor + x2;
|
||||
int y3 = y - y2;
|
||||
y2 = y - y2 / 2;
|
||||
Path p = new Path();
|
||||
p.moveTo(mConfig.initX, y);
|
||||
p.cubicTo(mConfig.initX, y - factor, x, y2 + factor, x, y2);
|
||||
p.moveTo(x, y2);
|
||||
p.cubicTo(x, y2 - factor, x2, y3 + factor, x2, y3);
|
||||
return p;
|
||||
}
|
||||
|
||||
public Config getmConfig() {
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
public abstract void start(View child, ViewGroup parent);
|
||||
|
||||
public static class Config {
|
||||
public int initX;
|
||||
public int initY;
|
||||
public int xRand;
|
||||
public int animLengthRand;
|
||||
public int bezierFactor;
|
||||
public int xPointFactor;
|
||||
public int animLength;
|
||||
public int heartWidth;
|
||||
public int heartHeight;
|
||||
public int animDuration;
|
||||
|
||||
public static Config fromTypeArray(TypedArray typedArray) {
|
||||
Config config = new Config();
|
||||
Resources res = typedArray.getResources();
|
||||
config.initX = (int) typedArray.getDimension(R.styleable.HeartLayout_initX,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_anim_init_x));
|
||||
config.initY = (int) typedArray.getDimension(R.styleable.HeartLayout_initY,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_anim_init_y));
|
||||
config.xRand = (int) typedArray.getDimension(R.styleable.HeartLayout_xRand,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_anim_bezier_x_rand));
|
||||
config.animLength = (int) typedArray.getDimension(R.styleable.HeartLayout_animLength,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_anim_length));
|
||||
config.animLengthRand = (int) typedArray.getDimension(R.styleable.HeartLayout_animLengthRand,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_anim_length_rand));
|
||||
config.bezierFactor = typedArray.getInteger(R.styleable.HeartLayout_bezierFactor,
|
||||
res.getInteger(R.integer.heart_anim_bezier_factor));
|
||||
config.xPointFactor = (int) typedArray.getDimension(R.styleable.HeartLayout_xPointFactor,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_anim_x_point_factor));
|
||||
config.heartWidth = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_width,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_size_width));
|
||||
config.heartHeight = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_height,
|
||||
res.getDimensionPixelOffset(R.dimen.heart_size_height));
|
||||
config.animDuration = typedArray.getInteger(R.styleable.HeartLayout_anim_duration,
|
||||
res.getInteger(R.integer.anim_duration));
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2015 tyrantgit
|
||||
*
|
||||
* 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.chwl.library.animator;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.view.animation.Transformation;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PathAnimator extends AbstractPathAnimator {
|
||||
private final AtomicInteger mCounter = new AtomicInteger(0);
|
||||
private Handler mHandler;
|
||||
|
||||
public PathAnimator(Config config) {
|
||||
super(config);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final View child, final ViewGroup parent) {
|
||||
parent.addView(child);
|
||||
FloatAnimation anim = new FloatAnimation(createPath(mCounter, parent, 2), randomRotation(), parent, child);
|
||||
anim.setDuration(mConfig.animDuration);
|
||||
anim.setInterpolator(new LinearInterpolator());
|
||||
anim.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
parent.removeView(child);
|
||||
}
|
||||
});
|
||||
mCounter.decrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
mCounter.incrementAndGet();
|
||||
}
|
||||
});
|
||||
anim.setInterpolator(new LinearInterpolator());
|
||||
child.startAnimation(anim);
|
||||
}
|
||||
|
||||
static class FloatAnimation extends Animation {
|
||||
private PathMeasure mPm;
|
||||
private View mView;
|
||||
private float mDistance;
|
||||
private float mRotation;
|
||||
|
||||
public FloatAnimation(Path path, float rotation, View parent, View child) {
|
||||
mPm = new PathMeasure(path, false);
|
||||
mDistance = mPm.getLength();
|
||||
mView = child;
|
||||
mRotation = rotation;
|
||||
parent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTransformation(float factor, Transformation transformation) {
|
||||
Matrix matrix = transformation.getMatrix();
|
||||
mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG);
|
||||
mView.setRotation(mRotation * factor);
|
||||
float scale = 1F;
|
||||
if (3000.0F * factor < 200.0F) {
|
||||
scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D);
|
||||
} else if (3000.0F * factor < 300.0F) {
|
||||
scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D);
|
||||
}
|
||||
mView.setScaleX(scale);
|
||||
mView.setScaleY(scale);
|
||||
transformation.setAlpha(1.0F - factor);
|
||||
}
|
||||
}
|
||||
|
||||
private static float scale(double a, double b, double c, double d, double e) {
|
||||
return (float) ((a - b) / (c - b) * (e - d) + d);
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.chwl.library.annatation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Created by huangmeng1 on 2018/5/7.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ActLayoutRes {
|
||||
int value();
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package com.chwl.library.base;
|
||||
|
||||
/**
|
||||
* <p> MVP 模式View 基类</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/7
|
||||
*/
|
||||
public interface IMvpBaseView {
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.chwl.library.base;
|
||||
|
||||
public enum PresenterEvent {
|
||||
|
||||
CREATE,
|
||||
START,
|
||||
RESUME,
|
||||
PAUSE,
|
||||
STOP,
|
||||
DESTROY
|
||||
|
||||
}
|
@@ -0,0 +1,145 @@
|
||||
package com.chwl.library.base.factory;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.trello.rxlifecycle3.LifecycleProvider;
|
||||
import com.trello.rxlifecycle3.LifecycleTransformer;
|
||||
import com.trello.rxlifecycle3.OutsideLifecycleException;
|
||||
import com.trello.rxlifecycle3.RxLifecycle;
|
||||
import com.chwl.library.base.IMvpBaseView;
|
||||
import com.chwl.library.base.PresenterEvent;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.functions.Function;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* <p>MVP模式Present 基类 ,跟Activity,Fragment等生命周期绑定</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/7
|
||||
*/
|
||||
public abstract class AbstractMvpPresenter<V extends IMvpBaseView> implements LifecycleProvider<PresenterEvent> {
|
||||
private static final String TAG = "Super-mvp";
|
||||
private final BehaviorSubject<PresenterEvent> lifecycleSubject = BehaviorSubject.create();
|
||||
protected V mMvpView;
|
||||
|
||||
/** 绑定View */
|
||||
public void attachMvpView(V mvpView) {
|
||||
|
||||
this.mMvpView = mvpView;
|
||||
logInfo("Presenter attachMvpView...");
|
||||
}
|
||||
|
||||
/** 解除绑定的View */
|
||||
public void detachMvpView() {
|
||||
mMvpView = null;
|
||||
logInfo("Presenter detachMvpView...");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取V层的接口View
|
||||
*
|
||||
* @return 当前的接口View
|
||||
*/
|
||||
public V getMvpView() {
|
||||
return mMvpView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presenter 被创建后调用
|
||||
*
|
||||
* @param saveState 被意外销毁后的Bundle
|
||||
*/
|
||||
public void onCreatePresenter(@Nullable Bundle saveState) {
|
||||
logInfo("Presenter onCreatePresenter...");
|
||||
lifecycleSubject.onNext(PresenterEvent.CREATE);
|
||||
}
|
||||
|
||||
public void onStartPresenter() {
|
||||
logInfo("Presenter onStartPresenter...");
|
||||
lifecycleSubject.onNext(PresenterEvent.START);
|
||||
}
|
||||
|
||||
public void onResumePresenter() {
|
||||
logInfo("Presenter onResumePresenter...");
|
||||
lifecycleSubject.onNext(PresenterEvent.RESUME);
|
||||
}
|
||||
|
||||
public void onPausePresenter() {
|
||||
logInfo("Presenter onPausePresenter...");
|
||||
lifecycleSubject.onNext(PresenterEvent.PAUSE);
|
||||
}
|
||||
|
||||
public void onStopPresenter() {
|
||||
logInfo("Presenter onStopPresenter...");
|
||||
lifecycleSubject.onNext(PresenterEvent.STOP);
|
||||
}
|
||||
|
||||
/** Presenter被销毁的时候调用,可以在此释放资源等 */
|
||||
public void onDestroyPresenter() {
|
||||
logInfo("Presenter onDestroyPresenter...");
|
||||
lifecycleSubject.onNext(PresenterEvent.DESTROY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在Presenter被意外销毁时调用,它的调用时机和Activity,Fragment,View中的onSaveInstanceState()方法调用时机相同
|
||||
*
|
||||
* @param outState 保存消息的Bundle
|
||||
*/
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
logInfo("Presenter onSaveInstanceState...");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Observable<PresenterEvent> lifecycle() {
|
||||
return lifecycleSubject.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public <T> LifecycleTransformer<T> bindUntilEvent(@NonNull PresenterEvent event) {
|
||||
return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public <T> LifecycleTransformer<T> bindToLifecycle() {
|
||||
return RxLifecycle.bind(lifecycleSubject, PRESENTER_LIFECYCLE);
|
||||
// return RxLifecycleAndroid.bindActivity(lifecycleSubject);
|
||||
}
|
||||
|
||||
private final Function<PresenterEvent, PresenterEvent> PRESENTER_LIFECYCLE =
|
||||
new Function<PresenterEvent, PresenterEvent>() {
|
||||
@Override
|
||||
public PresenterEvent apply(PresenterEvent lastEvent) throws Exception {
|
||||
switch (lastEvent) {
|
||||
case CREATE:
|
||||
return PresenterEvent.DESTROY;
|
||||
case START:
|
||||
return PresenterEvent.STOP;
|
||||
case RESUME:
|
||||
return PresenterEvent.PAUSE;
|
||||
case PAUSE:
|
||||
return PresenterEvent.STOP;
|
||||
case STOP:
|
||||
return PresenterEvent.DESTROY;
|
||||
case DESTROY:
|
||||
throw new OutsideLifecycleException("Cannot bind to Presenter lifecycle when outside of it.");
|
||||
default:
|
||||
throw new UnsupportedOperationException("Binding to " + lastEvent + " not yet implemented");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void logInfo(String msg) {
|
||||
if (false)
|
||||
Log.e(TAG, msg);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,144 @@
|
||||
package com.chwl.library.base.factory;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.base.IMvpBaseView;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
|
||||
/**
|
||||
* <p> 管理Presenter的声明周期以及与View之间的关联</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/8
|
||||
*/
|
||||
public class BaseMvpProxy<V extends IMvpBaseView, P extends AbstractMvpPresenter<V>>
|
||||
implements PresenterProxyInterface<V, P> {
|
||||
private static final String TAG = "Super-mvp";
|
||||
private static final String KEY_PRESENTER = "key_presenter";
|
||||
|
||||
private PresenterMvpFactory<V, P> mMvpFactory;
|
||||
private P mPresenter;
|
||||
private Bundle mBundle;
|
||||
private boolean mIsAttachView;
|
||||
|
||||
public BaseMvpProxy(PresenterMvpFactory<V, P> mvpFactory) {
|
||||
this.mMvpFactory = mvpFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setPresenterFactory(PresenterMvpFactory<V, P> presenterFactory) {
|
||||
if (mPresenter != null) {
|
||||
throw new IllegalArgumentException(ResUtil.getString(R.string.base_factory_basemvpproxy_01));
|
||||
}
|
||||
this.mMvpFactory = presenterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PresenterMvpFactory<V, P> getPresenterFactory() {
|
||||
return mMvpFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public P getMvpPresenter() {
|
||||
logInfo("Proxy getMvpPresenter...");
|
||||
//如果之前创建过且是意外销毁则从Bundle中恢复
|
||||
if (mMvpFactory != null) {
|
||||
if (mPresenter == null) {
|
||||
mPresenter = mMvpFactory.createMvpPresenter();
|
||||
mPresenter.onCreatePresenter(mBundle == null ? null : mBundle.getBundle(KEY_PRESENTER));
|
||||
}
|
||||
}
|
||||
logInfo("Proxy getMvpPresenter..." + mPresenter);
|
||||
return mPresenter;
|
||||
}
|
||||
|
||||
/** 销毁Presenter持有的View */
|
||||
private void onDetachMvpView() {
|
||||
logInfo("Proxy onDetachMvpView...");
|
||||
if (mPresenter != null && mIsAttachView) {
|
||||
mPresenter.detachMvpView();
|
||||
mIsAttachView = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void onStart() {
|
||||
logInfo("Proxy onStart...");
|
||||
if (mPresenter != null)
|
||||
mPresenter.onStartPresenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定Presenter与View
|
||||
*
|
||||
* @param mvpView 当前view接口类型
|
||||
*/
|
||||
public void onResume(V mvpView) {
|
||||
getMvpPresenter();
|
||||
logInfo("Proxy onResume...");
|
||||
if (mPresenter != null && !mIsAttachView) {
|
||||
mPresenter.attachMvpView(mvpView);
|
||||
mIsAttachView = true;
|
||||
mPresenter.onResumePresenter();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
logInfo("Proxy onPause...");
|
||||
if (mPresenter != null)
|
||||
mPresenter.onPausePresenter();
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
logInfo("Proxy onStop...");
|
||||
if (mPresenter != null)
|
||||
mPresenter.onStopPresenter();
|
||||
}
|
||||
|
||||
|
||||
/** 销毁Presenter */
|
||||
public void onDestroy() {
|
||||
logInfo("Proxy onDestroy...");
|
||||
if (mPresenter != null) {
|
||||
onDetachMvpView();
|
||||
mPresenter.onDestroyPresenter();
|
||||
mPresenter = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 意外销毁的时候调用
|
||||
*
|
||||
* @return Bundle ,存入回调给Presenter的Bundle和当前Presenter的id,在调用方(activity)中保存
|
||||
*/
|
||||
public Bundle onSaveInstanceState() {
|
||||
logInfo("Proxy onSaveInstanceState...");
|
||||
Bundle bundle = new Bundle();
|
||||
getMvpPresenter();
|
||||
if (mPresenter != null) {
|
||||
Bundle presenterBundle = new Bundle();
|
||||
//回到Presenter
|
||||
mPresenter.onSaveInstanceState(presenterBundle);
|
||||
//保存presenterBundle
|
||||
bundle.putBundle(KEY_PRESENTER, presenterBundle);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/***
|
||||
* 意外关闭Presenter的时候恢复Presenter
|
||||
* @param saveInstanceState 意外关闭Presenter时存储的Bundle
|
||||
*/
|
||||
public void onRestoreInstanceState(Bundle saveInstanceState) {
|
||||
logInfo("Proxy onRestoreInstanceState... Presenter=" + mPresenter);
|
||||
mBundle = saveInstanceState;
|
||||
}
|
||||
|
||||
private void logInfo(String msg) {
|
||||
if (false)
|
||||
Log.e(TAG, msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.chwl.library.base.factory;
|
||||
|
||||
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* <p> 标注创建Presenter的注解</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/8
|
||||
*/
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CreatePresenter {
|
||||
Class<? extends AbstractMvpPresenter> value();
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.chwl.library.base.factory;
|
||||
|
||||
|
||||
import com.chwl.library.base.IMvpBaseView;
|
||||
|
||||
/**
|
||||
* <p> Presenter 工厂接口</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/7
|
||||
*/
|
||||
public interface PresenterMvpFactory<V extends IMvpBaseView, P extends AbstractMvpPresenter<V>> {
|
||||
/**
|
||||
* 创建Presenter方法
|
||||
*
|
||||
* @return 创建的Presenter
|
||||
*/
|
||||
P createMvpPresenter();
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package com.chwl.library.base.factory;
|
||||
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.base.IMvpBaseView;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
/**
|
||||
* <p> Presenter 工厂实现类 </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/8
|
||||
*/
|
||||
public class PresenterMvpFactoryImpl<V extends IMvpBaseView, P extends AbstractMvpPresenter<V>>
|
||||
implements PresenterMvpFactory<V, P> {
|
||||
private final Class<P> mPresenterClass;
|
||||
|
||||
private PresenterMvpFactoryImpl(Class<P> presenterClass) {
|
||||
this.mPresenterClass = presenterClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据注解创建Presenter的工厂实现方法
|
||||
*
|
||||
* @param viewClass 需要创建Presenter的V层实现类
|
||||
* @param <V> 当前View的实现接口类型
|
||||
* @param <P> 当前要创建的Presenter类型
|
||||
* @return 工厂实现类
|
||||
*/
|
||||
public static <V extends IMvpBaseView, P extends AbstractMvpPresenter<V>> PresenterMvpFactoryImpl<V, P>
|
||||
createFactory(Class<?> viewClass) {
|
||||
CreatePresenter annotation = viewClass.getAnnotation(CreatePresenter.class);
|
||||
Class<P> pClass = null;
|
||||
if (annotation != null) {
|
||||
pClass = (Class<P>) annotation.value();
|
||||
}
|
||||
return pClass == null ? null : new PresenterMvpFactoryImpl<>(pClass);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public P createMvpPresenter() {
|
||||
try {
|
||||
return mPresenterClass.newInstance();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(ResUtil.getString(R.string.base_factory_presentermvpfactoryimpl_01), e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package com.chwl.library.base.factory;
|
||||
|
||||
|
||||
import com.chwl.library.base.IMvpBaseView;
|
||||
|
||||
/**
|
||||
* <p> 提供设置工厂,获取工厂,获取Presenter的方法,由V层实现这个接口</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/8
|
||||
*/
|
||||
public interface PresenterProxyInterface<V extends IMvpBaseView, P extends AbstractMvpPresenter<V>> {
|
||||
|
||||
/**
|
||||
* 设置Presenter的工厂类,这个方法只能在getMvpPresenter()之前调用,如果Presenter已经创建了则不能再更改
|
||||
*
|
||||
* @param presenterFactory 要设置的Presenter的工厂类型
|
||||
*/
|
||||
void setPresenterFactory(PresenterMvpFactory<V, P> presenterFactory);
|
||||
|
||||
/**
|
||||
* 获取Presenter的工厂类
|
||||
*
|
||||
* @return 返回的是PresenterMvpFactory类型实例
|
||||
*/
|
||||
PresenterMvpFactory<V, P> getPresenterFactory();
|
||||
|
||||
/**
|
||||
* 获取创建的Presenter
|
||||
*
|
||||
* @return 指定的目标Presenter
|
||||
*/
|
||||
P getMvpPresenter();
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.chwl.library.bindinglist;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Created by lvzebiao on 2018/10/25.
|
||||
*/
|
||||
|
||||
public abstract class BaseItem<T> implements IItem {
|
||||
|
||||
protected Context context;
|
||||
|
||||
public T data;
|
||||
|
||||
protected int spanSize;
|
||||
|
||||
public BaseItem(Context context, T data) {
|
||||
this(context, data, 1);
|
||||
}
|
||||
|
||||
public BaseItem(Context context, T data, int spanSize) {
|
||||
this.context = context;
|
||||
this.data = data;
|
||||
this.spanSize = spanSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpanSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public T data() {
|
||||
return data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.chwl.library.bindinglist;
|
||||
|
||||
/**
|
||||
* Created by lvzebiao on 2018/10/23.
|
||||
*/
|
||||
public interface IItem {
|
||||
/**
|
||||
* 格子布局时需要用到,默认返回1,线性布局忽略即可
|
||||
*/
|
||||
int getSpanSize();
|
||||
|
||||
int getType();
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package com.chwl.library.bindinglist;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.databinding.ViewDataBinding;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Created by lvzebiao on 2018/10/23.
|
||||
*/
|
||||
|
||||
public class ItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
protected final ViewDataBinding binding;
|
||||
|
||||
public ItemViewHolder(ViewDataBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
public void bindTo(int variableId, IItem item) {
|
||||
binding.setVariable(variableId, item);
|
||||
binding.executePendingBindings();
|
||||
}
|
||||
|
||||
public static ItemViewHolder create(ViewGroup parent, int viewType) {
|
||||
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
|
||||
viewType, parent, false);
|
||||
return new ItemViewHolder(binding);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,353 @@
|
||||
package com.chwl.library.bindinglist;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.databinding.ObservableArrayList;
|
||||
import androidx.databinding.ObservableList;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by lvzebiao on 2018/10/23.
|
||||
*/
|
||||
|
||||
public class MultiTypeAdapter<T extends IItem> extends RecyclerView.Adapter<ItemViewHolder> {
|
||||
|
||||
private final WeakReferenceOnListChangedCallback<T> callback = new WeakReferenceOnListChangedCallback<>(this);
|
||||
private List<T> headersItems = new ArrayList<>();
|
||||
private ObservableList<T> footersItems = new ObservableArrayList<>();
|
||||
/**
|
||||
* 此数据源表示真正的数据, 保留用于其他逻辑计算
|
||||
*/
|
||||
private List<T> dataItems;
|
||||
/**
|
||||
* 此list表示RecyclerView显示的所有item,即header+data+footer
|
||||
*/
|
||||
private ObservableList<T> allItems = new ObservableArrayList<>();
|
||||
private boolean canLoadMore = false;
|
||||
private RecyclerView recyclerView;
|
||||
private int variableId;
|
||||
private boolean needItemClick;
|
||||
private T loadMoreItem;
|
||||
private List<T> statusList = new ArrayList<>();
|
||||
private T startLoadingItem;
|
||||
private T netErrorItem;
|
||||
private T emptyItem;
|
||||
private OnItemClickListener<T> listener;
|
||||
|
||||
public MultiTypeAdapter(List<T> list, int variableId, boolean needItemClick) {
|
||||
if (list == null) {
|
||||
list = new ObservableArrayList<>();
|
||||
}
|
||||
this.dataItems = list;
|
||||
this.allItems.addAll(this.dataItems);
|
||||
this.variableId = variableId;
|
||||
this.needItemClick = needItemClick;
|
||||
}
|
||||
|
||||
public MultiTypeAdapter(int variableId, boolean needItemClick) {
|
||||
this(null, variableId, needItemClick);
|
||||
}
|
||||
|
||||
public MultiTypeAdapter(int variableId) {
|
||||
this(null, variableId, false);
|
||||
}
|
||||
|
||||
static void ensureChangeOnMainThread() {
|
||||
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
|
||||
throw new IllegalStateException("You must only modify the ObservableList on the main thread.");
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> getHeadersItems() {
|
||||
return headersItems;
|
||||
}
|
||||
|
||||
public List<T> getData() {
|
||||
return dataItems;
|
||||
}
|
||||
|
||||
public List<T> getAllItems() {
|
||||
return allItems;
|
||||
}
|
||||
|
||||
public void setCanLoadMore(boolean canLoadMore) {
|
||||
this.canLoadMore = canLoadMore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这方法不会清除header,清除header调用
|
||||
* {@link #clearAllItem()}
|
||||
*/
|
||||
public void clearData() {
|
||||
//清除数据源,也要清除底部加载更多
|
||||
dataItems.clear();
|
||||
statusList.clear();
|
||||
footersItems.clear();
|
||||
hideStatus();
|
||||
if (headersItems.size() == 0) {
|
||||
allItems.clear();
|
||||
} else {
|
||||
// List<T> tempList = new ArrayList<>();
|
||||
// for (int i = headersItems.size(); i < allItems.size(); i++) {
|
||||
// tempList.add(allItems.get(i));
|
||||
// }
|
||||
// allItems.removeAll(tempList);
|
||||
allItems.clear();
|
||||
allItems.addAll(headersItems);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有列表,这个方法比较少调用
|
||||
* 一般header不会清除,只清除data部分调用
|
||||
* {@link #clearData()}
|
||||
*/
|
||||
public void clearAllItem() {
|
||||
dataItems.clear();
|
||||
statusList.clear();
|
||||
footersItems.clear();
|
||||
headersItems.clear();
|
||||
allItems.clear();
|
||||
}
|
||||
|
||||
public void addData(List<T> list) {
|
||||
dataItems.addAll(list);
|
||||
int insertPos = allItems.size() - footersItems.size();
|
||||
if (insertPos < 0) {
|
||||
insertPos = 0;
|
||||
}
|
||||
allItems.addAll(insertPos, list);
|
||||
}
|
||||
|
||||
public void addHeaderItem(T headerItem) {
|
||||
headersItems.add(headerItem);
|
||||
allItems.add(0, headerItem);
|
||||
}
|
||||
|
||||
public void setLoadMoreView(T item) {
|
||||
if (!canLoadMore || item == null) {
|
||||
return;
|
||||
}
|
||||
if (footersItems.size() == 0) {
|
||||
if (loadMoreItem == null) {
|
||||
loadMoreItem = item;
|
||||
}
|
||||
footersItems.add(loadMoreItem);
|
||||
allItems.add(loadMoreItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeLoadMoreView() {
|
||||
if (!canLoadMore || loadMoreItem == null) {
|
||||
return;
|
||||
}
|
||||
if (footersItems.size() == 1) {
|
||||
footersItems.clear();
|
||||
if (allItems.contains(loadMoreItem)) {
|
||||
allItems.remove(loadMoreItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 此方法并不是所有list都需要,不需要则传null
|
||||
* 只是客户端有个加载动画,所以添加
|
||||
*/
|
||||
public void startLoading(T item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (statusList.size() == 0) {
|
||||
if (startLoadingItem == null) {
|
||||
startLoadingItem = item;
|
||||
}
|
||||
statusList.add(startLoadingItem);
|
||||
allItems.add(startLoadingItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果有数据不添加错误item
|
||||
*/
|
||||
public void setNetError(T item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (statusList.size() == 0) {
|
||||
if (netErrorItem == null) {
|
||||
netErrorItem = item;
|
||||
}
|
||||
statusList.add(netErrorItem);
|
||||
allItems.add(netErrorItem);
|
||||
} else {
|
||||
if (emptyItem != null && allItems.contains(emptyItem)) {
|
||||
allItems.remove(emptyItem);
|
||||
statusList.clear();
|
||||
setNetError(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEmpty(T item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (statusList.size() == 0) {
|
||||
if (emptyItem == null) {
|
||||
emptyItem = item;
|
||||
}
|
||||
statusList.add(emptyItem);
|
||||
allItems.add(emptyItem);
|
||||
} else {
|
||||
//说明状态列表有item,可能是error,也可能就是empty
|
||||
//1.如果是empty则不需要操作,此操作相当于空列表,再刷新还是空列表,error同理
|
||||
//2.如果是error,先移除,再添加empty
|
||||
if (netErrorItem != null && allItems.contains(netErrorItem)) {
|
||||
allItems.remove(netErrorItem);
|
||||
statusList.clear();
|
||||
setEmpty(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hideStatus() {
|
||||
statusList.clear();
|
||||
if (startLoadingItem != null) {
|
||||
allItems.remove(startLoadingItem);
|
||||
}
|
||||
|
||||
if (netErrorItem != null) {
|
||||
allItems.remove(netErrorItem);
|
||||
}
|
||||
if (emptyItem != null) {
|
||||
allItems.remove(emptyItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLoarMoreView() {
|
||||
footersItems.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return allItems.get(position).getType();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return ItemViewHolder.create(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
|
||||
final T item = getIndexItem(position);
|
||||
holder.bindTo(variableId, item);
|
||||
if (needItemClick) {
|
||||
holder.binding.getRoot().setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
listener.onClick(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return allItems.size();
|
||||
}
|
||||
|
||||
public T getIndexItem(int position) {
|
||||
return allItems.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
if (this.recyclerView == null && allItems != null) {
|
||||
allItems.addOnListChangedCallback(callback);
|
||||
}
|
||||
this.recyclerView = recyclerView;
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener<T> listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
if (this.recyclerView != null && allItems != null) {
|
||||
allItems.removeOnListChangedCallback(callback);
|
||||
}
|
||||
this.recyclerView = null;
|
||||
}
|
||||
|
||||
public interface OnItemClickListener<T extends IItem> {
|
||||
void onClick(T item);
|
||||
}
|
||||
|
||||
private static class WeakReferenceOnListChangedCallback<T extends IItem> extends ObservableList.OnListChangedCallback<ObservableList<T>> {
|
||||
final WeakReference<MultiTypeAdapter<T>> adapterRef;
|
||||
|
||||
WeakReferenceOnListChangedCallback(MultiTypeAdapter<T> adapter) {
|
||||
this.adapterRef = new WeakReference<>(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(ObservableList sender) {
|
||||
MultiTypeAdapter<T> adapter = adapterRef.get();
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
ensureChangeOnMainThread();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(ObservableList sender, final int positionStart, final int itemCount) {
|
||||
MultiTypeAdapter<T> adapter = adapterRef.get();
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
ensureChangeOnMainThread();
|
||||
adapter.notifyItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(ObservableList sender, final int positionStart, final int itemCount) {
|
||||
MultiTypeAdapter<T> adapter = adapterRef.get();
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
ensureChangeOnMainThread();
|
||||
adapter.notifyItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(ObservableList sender, final int fromPosition, final int toPosition, final int itemCount) {
|
||||
MultiTypeAdapter<T> adapter = adapterRef.get();
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
ensureChangeOnMainThread();
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
adapter.notifyItemMoved(fromPosition + i, toPosition + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(ObservableList sender, final int positionStart, final int itemCount) {
|
||||
MultiTypeAdapter<T> adapter = adapterRef.get();
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
ensureChangeOnMainThread();
|
||||
adapter.notifyItemRangeRemoved(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package com.chwl.library.constants;
|
||||
|
||||
public @interface ConstantsLib {
|
||||
public @interface Key{
|
||||
String Permissions_Img = "Permissions_Img";
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 每个core实现类都应该继承此类
|
||||
* 提供一些基础设施给子类使用
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chwl.library.utils.config.BasicConfig;
|
||||
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractBaseCore implements IBaseCore {
|
||||
|
||||
public AbstractBaseCore() {
|
||||
// 确保有默认构造函数
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return BasicConfig.INSTANCE.getAppContext();
|
||||
}
|
||||
|
||||
|
||||
protected void notifyClients(Class<? extends ICoreClient> clientClass, String methodName, Object... args) {
|
||||
CoreManager.notifyClients(clientClass, methodName, args);
|
||||
}
|
||||
|
||||
protected void notifyClients(Class<? extends ICoreClient> clientClass, String methodName) {
|
||||
CoreManager.notifyClients(clientClass, methodName);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*/
|
||||
public class CoreError {
|
||||
|
||||
// error code 定义
|
||||
public static final int NETWORK_ERROR = 1000;
|
||||
public static final int DB_ERROR = 1001;
|
||||
public static final int TIMEOUT_ERROR = 1002;
|
||||
public static final int SERVER_ERROR = 1003;
|
||||
public static final int UNKNOWN_ERROR = 1004;
|
||||
public static final int TRAFFIC_FORBIDDEN_ERROR = 1005;
|
||||
public static final int ILLEGAL_ACCESS_ERROR = 1006;//非法访问
|
||||
public static final int ILLEGAL_SMS_CODE_ERROR = 1007;//非法短信验证码
|
||||
public static final int DUPLICATE_ERROR = 1008; // 存在重复的记录
|
||||
public static final int NEWTORK_ERROR_ONLY_WIFI = 1009; // 仅wifi下可以访问网络
|
||||
public static final int SERVER_ERROR_USER_FORBIDDEN = 1010; // 黑名单用户
|
||||
public static final int SERVER_ERROR_ILLEGAL_CONTENT = 1011; // 内容不合法
|
||||
public static final int DATA_NOT_FOUND = 1404; // 数据找不到
|
||||
|
||||
//JX 错误吗定义
|
||||
public static final int ACCESS_DENIED = 100 ;//拒绝访问
|
||||
|
||||
public static final int INVALID_REQUEST = 101;//请求不合法
|
||||
|
||||
public static final int INVALID_REQUEST_SCHEME = 102;//错误的请求协议
|
||||
|
||||
public static final int INVALID_REQUEST_METHOD = 103;//错误的请求方法
|
||||
|
||||
public static final int INVALID_CLIENT_ID = 104; //client id不存在或已删除
|
||||
|
||||
public static final int CLIENT_ID_IS_BLOCKED = 105;//client id已被禁用
|
||||
|
||||
public static final int UNAUTHORIZED_CLIENT_ID = 106; //client id未授权
|
||||
|
||||
public static final int USERNAME_PASSWORD_MISMATCH = 107; //用户名密码不匹配
|
||||
|
||||
public static final int INVALID_REQUEST_SCOPE = 108; //访问的scope不合法,开发者不用太关注,一般不会出现该错误
|
||||
|
||||
public static final int INVALID_USER = 109; //用户不存在或已删除
|
||||
|
||||
public static final int USER_HAS_BLOCKED = 110; //用户已被屏蔽
|
||||
|
||||
public static final int INVALID_TOKEN = 111;//token不存在或已被用户删除,或者用户修改了密码
|
||||
|
||||
public static final int ACCESS_TOKEN_IS_MISSING = 112; //未找到access_token
|
||||
|
||||
public static final int ACCESS_TOKEN_HAS_EXPIRED = 113;//access_token已过期
|
||||
|
||||
public static final int INVALID_REQUEST_URI = 114; //请求地址未注册
|
||||
|
||||
public static final int INVALID_CREDENTIAL_1 = 115; //用户未授权访问此数据
|
||||
|
||||
public static final int INVALID_CREDENTIAL_2 = 116; //client id未申请此权限
|
||||
|
||||
public static final int NOT_TRIAL_USER = 117; //未注册的测试用户
|
||||
|
||||
public static final int REQUIRED_PARAMETER_IS_MISSING = 118; //缺少参数
|
||||
|
||||
public static final int INVALID_GRANT = 119;//invalid grant type"
|
||||
|
||||
public static final int UNSUPPORTED_GRANT_TYPE = 120;//错误的grant_type
|
||||
|
||||
public static final int UNSUPPORTED_RESPONSE_TYPE = 121; //错误的response_type
|
||||
|
||||
public static final int CLIENT_SECRET_MISMATCH = 122; //client_secret不匹配
|
||||
|
||||
public static final int REDIRECT_URI_MISMATCH = 123; //redirect_uri不匹配
|
||||
|
||||
public static final int INVALID_AUTHORIZATION_CODE = 124;//authorization_code不存在或已过期
|
||||
|
||||
public static final int ACCESS_TOKEN_HAS_EXPIRED_SINCE_PASSWORD_CHANGED = 125;//因用户修改密码而导致access_token过期
|
||||
|
||||
public static final int ACCESS_TOKEN_HAS_NOT_EXPIRED = 126; //access_token未过期;
|
||||
|
||||
public static final int UNSUPPORTED_TICKET_ISSUE_TYPE = 127;//unsupported ticket issue type"
|
||||
|
||||
public static final int INVALID_TICKET = 128;//ticket不存在或已过期
|
||||
|
||||
public static final int TICKET_IS_MISSING = 129; //未找到ticket
|
||||
|
||||
public static final int TICKET_HAS_EXPIRED = 130; //ticket过期
|
||||
|
||||
public static final int TICKET_HAS_NOT_EXPIRED = 131; //ticket未过期
|
||||
|
||||
public static final int TICKET_HAS_EXPIRED_SINCE_PASSWORD_CHANGED = 132; //因为用户修改密码而ticket过期
|
||||
|
||||
public static final int INVALID_SCOPE = 133;
|
||||
|
||||
public static final int RATE_LIMIT_EXCEEDED1 = 134;//用户访问速度限制
|
||||
|
||||
public static final int RATE_LIMIT_EXCEEDED2 = 135;//IP访问速度限制
|
||||
|
||||
public static final int INVALID_IDENTIFYING_CODE = 150; //不可用的验证码
|
||||
|
||||
public static final int INVALID_USERNAME = 151; //用户名不合法
|
||||
|
||||
public static final int USER_HAS_SIGNED_UP = 152;//用户名已被注册
|
||||
|
||||
public static final int INVALID_RESET_CODE = 153;//重置码无效
|
||||
|
||||
public static final int INVALID_NICK = 161; //昵称不合法
|
||||
|
||||
public static final int INVALID_THIRD_TOKEN = 162; //第三方token不合法
|
||||
|
||||
public static final int THIRD_ACCOUNT_HAVE_BIND = 163; //第三方账户已经绑定或之前已使用该账户登陆过系统
|
||||
|
||||
public static final int UNBIND_OPENID_NOT_MATCH = 164; //账户解绑失败
|
||||
|
||||
public static final int UNBIND_MAIN_ACCOUNT = 165;//解绑主账户错误
|
||||
|
||||
public static final int SUCCESS = 200; //成功
|
||||
|
||||
public static final int INVALID_SERVICE = 199;//服务不可用
|
||||
|
||||
public static final int UNKNOWN = 999;//未知错误
|
||||
|
||||
|
||||
// 登录错误码
|
||||
public static final int AUTH_USER_NOT_EXIST = 2000;
|
||||
public static final int AUTH_PASSWORD_ERROR = 2001;
|
||||
public static final int AUTH_USER_BANNED = 2002;
|
||||
public static final int NO_LOGIN_ERROR = 2003;//没有登录
|
||||
public static final int NEED_RELOGIN_ERROR = 2004;//需要重新登录
|
||||
public static final int LOGIN_EXPIRED_ERROR = 2005;//登录信息过期
|
||||
public static final int REGISTER_ERROR = 2006;//注册失败
|
||||
public static final int THIRD_PARTY_LOGIN_ERROR = 2007;//第三方登录失败
|
||||
public static final int THIRD_PARTY_BIND_TOKEN_INVALID = 2008;//第三方绑定失败, token 无效
|
||||
public static final int THIRD_PARTY_BIND_ACCOUNT_HAVE_BIND = 2009;//第三方绑定失败, 第三方账户已经绑定或之前已使用该账户登陆过系统
|
||||
public static final int THIRD_PARTY_UNBIND_ERROR = 2010;//第三方解绑失败
|
||||
|
||||
// 文件错误码
|
||||
public static final int FILE_ERROR = 3000;//文件通用错误
|
||||
public static final int FILE_NO_ENOUGH_SPACE = 3001;//磁盘剩余空间不够
|
||||
|
||||
public int code;
|
||||
public String message;
|
||||
public Throwable throwable;
|
||||
|
||||
public static CoreError empty() {
|
||||
return new CoreError(-1, null);
|
||||
}
|
||||
|
||||
public CoreError(int code, String message) {
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public CoreError(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public CoreError(int code, String message, Throwable throwable) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.throwable = throwable;
|
||||
}
|
||||
|
||||
public static CoreError serverError() {
|
||||
CoreError error = empty();
|
||||
error.code = SERVER_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError timeoutError() {
|
||||
CoreError error = empty();
|
||||
error.code = TIMEOUT_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError networkError() {
|
||||
CoreError error = empty();
|
||||
error.code = NETWORK_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError dbError() {
|
||||
CoreError error = empty();
|
||||
error.code = DB_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError unkonwnError() {
|
||||
CoreError error = empty();
|
||||
error.code = UNKNOWN_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError noLoginError() {
|
||||
CoreError error = empty();
|
||||
error.code = NO_LOGIN_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError needReloginError() {
|
||||
CoreError error = empty();
|
||||
error.code = NEED_RELOGIN_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError duplicateError() {
|
||||
CoreError error = empty();
|
||||
error.code = DUPLICATE_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError onlyWifiAccessError() {
|
||||
CoreError error = empty();
|
||||
error.code = NEWTORK_ERROR_ONLY_WIFI;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError userForbiddenError() {
|
||||
CoreError error = empty();
|
||||
error.code = SERVER_ERROR_USER_FORBIDDEN;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError illegalContentError() {
|
||||
CoreError error = empty();
|
||||
error.code = SERVER_ERROR_ILLEGAL_CONTENT;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError thirdpartyLoginError() {
|
||||
CoreError error = empty();
|
||||
error.code = THIRD_PARTY_LOGIN_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError thirdpartyBindError() {
|
||||
CoreError error = empty();
|
||||
error.code = THIRD_PARTY_BIND_TOKEN_INVALID;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError thirdpartyUnbindError() {
|
||||
CoreError error = empty();
|
||||
error.code = THIRD_PARTY_UNBIND_ERROR;
|
||||
return error;
|
||||
}
|
||||
|
||||
public static CoreError dataNotFoundError() {
|
||||
CoreError error = empty();
|
||||
error.code = DATA_NOT_FOUND;
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean isDuplicateError() {
|
||||
return code == DUPLICATE_ERROR;
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 用于使用annotation实现监听某个client的某个回调
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
*
|
||||
* @auth zhongyongsheng
|
||||
*/
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface CoreEvent {
|
||||
|
||||
Class<?> coreClientClass();
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 用于使用annotation实现监听某个client的某个回调
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*
|
||||
*/
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface CoreEventListener {
|
||||
|
||||
//String eventSource();
|
||||
|
||||
Class<?> eventClass();
|
||||
|
||||
/**
|
||||
* 所监听的接口的方法名
|
||||
* @return
|
||||
*/
|
||||
String eventName();
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 用于封装core层的异常
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*
|
||||
*/
|
||||
public class CoreException extends Exception {
|
||||
|
||||
private CoreError error;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CoreException(CoreError coreError) {
|
||||
super(coreError.message, coreError.throwable);
|
||||
error = coreError;
|
||||
}
|
||||
|
||||
public CoreException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public CoreException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public CoreException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public CoreError getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Core对象工厂。使用getCore前需要注册对应core接口实现类
|
||||
* 此类是非线程安全的,必须在主线程调用
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
|
||||
import com.chwl.library.utils.log.MLog;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*
|
||||
*/
|
||||
public class CoreFactory {
|
||||
|
||||
private static final HashMap<Class<? extends IBaseCore>, IBaseCore> cores;
|
||||
private static final HashMap<Class<? extends IBaseCore>, Class<? extends AbstractBaseCore>> coreClasses;
|
||||
static {
|
||||
cores = new HashMap<>();
|
||||
coreClasses = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从工厂获取实现cls接口的对象实例
|
||||
* 该实例是使用registerCoreClass注册的实现类的对象
|
||||
*
|
||||
* @param cls 必须是core接口类,不能是core实现类,否则会抛出异常
|
||||
* @return 如果生成对象失败,返回null
|
||||
*/
|
||||
public static <T extends IBaseCore> T getCore(Class<T> cls) {
|
||||
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
IBaseCore core = cores.get(cls);
|
||||
if (core == null) {
|
||||
Class<? extends AbstractBaseCore> implClass = coreClasses.get(cls);
|
||||
if (implClass == null) {
|
||||
if (cls.isInterface()) {
|
||||
MLog.error("CoreFactory", "No registered core class for: " + cls.getName());
|
||||
throw new IllegalArgumentException("No registered core class for: " + cls.getName());
|
||||
} else {
|
||||
MLog.error("CoreFactory", "Not interface core class for: " + cls.getName());
|
||||
throw new IllegalArgumentException("Not interface core class for: " + cls.getName());
|
||||
}
|
||||
} else {
|
||||
core = implClass.newInstance();
|
||||
}
|
||||
|
||||
if (core != null) {
|
||||
cores.put(cls, core);
|
||||
//MLog.debug("CoreFactory", cls.getName() + " created: "
|
||||
// + ((implClass != null) ? implClass.getName() : cls.getName()));
|
||||
}
|
||||
}
|
||||
return (T)core;
|
||||
} catch (Throwable e) {
|
||||
MLog.error("CoreFactory", "getCore() failed for: " + cls.getName(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册某个接口实现类
|
||||
* @param coreInterface
|
||||
* @param coreClass
|
||||
*/
|
||||
public static void registerCoreClass(Class<? extends IBaseCore> coreInterface, Class<? extends AbstractBaseCore> coreClass) {
|
||||
|
||||
if (coreInterface == null || coreClass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
coreClasses.put(coreInterface, coreClass);
|
||||
MLog.debug("CoreFactory", "registered class " + coreClass.getName() + " for core: " + coreInterface.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回某个接口是否有注册实现类
|
||||
* @param coreInterface
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasRegisteredCoreClass(Class<? extends IBaseCore> coreInterface) {
|
||||
if (coreInterface == null) {
|
||||
return false;
|
||||
} else {
|
||||
return coreClasses.containsKey(coreInterface);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* 管理core对象的类。外部应该使用此类的接口来获取某个core对象
|
||||
* 它使用CoreFactory来声成core对象实例。
|
||||
* 上层未注册core实现类话,注册一个默认实现(调用init函数)
|
||||
* 此类不是线程安全的,应该只在主线程调用
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.chwl.library.utils.log.MLog;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*/
|
||||
public class CoreManager {
|
||||
|
||||
public static final String TAG = "CoreManager";
|
||||
public static final String TAG_EVENT = "CoreManager_Event";
|
||||
|
||||
private static Map<Class<? extends ICoreClient>, CopyOnWriteArraySet<ICoreClient>> clients
|
||||
= new HashMap<>();
|
||||
|
||||
private static Map<Class<?>, CopyOnWriteArraySet<Object>> coreEvents = new HashMap<>();
|
||||
|
||||
private static Map<Class<? extends ICoreClient>, Map<String, Method>> clientMethods
|
||||
= new HashMap();
|
||||
|
||||
private static Map<Object, Map<String, Method>> coreEventMethods = new HashMap();
|
||||
|
||||
private static Context context;
|
||||
|
||||
public CoreManager() {
|
||||
}
|
||||
|
||||
public static void init(String logDir) {
|
||||
if(!TextUtils.isEmpty(logDir)) {
|
||||
MLog.LogOptions options = new MLog.LogOptions();
|
||||
options.uniformTag = TAG;
|
||||
MLog.initialize(logDir, options);
|
||||
}
|
||||
|
||||
MLog.info(TAG, "--------------------------------CoreManager init--------------------------------", new Object[0]);
|
||||
}
|
||||
|
||||
public static void onTerminate() {
|
||||
MLog.close();
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
private static void addClientMethodsIfNeeded(Class<? extends ICoreClient> clientClass) {
|
||||
try {
|
||||
Map<String, Method> methods = clientMethods.get(clientClass);
|
||||
if (methods == null) {
|
||||
methods = new HashMap<String, Method>();
|
||||
Method[] allMethods = clientClass.getMethods();
|
||||
for (Method m : allMethods) {
|
||||
methods.put(m.getName(), m);
|
||||
}
|
||||
clientMethods.put(clientClass, methods);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
MLog.error(TAG, throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听某个接口的回调,监听者需要实现该接口
|
||||
* 注意在不需要回调时要用removeClient
|
||||
*
|
||||
* @param clientClass
|
||||
* @param client
|
||||
*/
|
||||
public static void addClient(Class<? extends ICoreClient> clientClass, ICoreClient client) {
|
||||
|
||||
if (clientClass == null || client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CopyOnWriteArraySet<ICoreClient> clientList = clients.get(clientClass);
|
||||
if (clientList == null) {
|
||||
clientList = new CopyOnWriteArraySet<ICoreClient>();
|
||||
clients.put(clientClass, clientList);
|
||||
}
|
||||
|
||||
addClientMethodsIfNeeded(clientClass);
|
||||
|
||||
if (clientList.contains(client)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.add(client);
|
||||
//MLog.verbose(TAG, "client(" + client + ") added for " + clientClass.getName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void addClient(ICoreClient client, Class<?> clientClass) {
|
||||
if (clientClass == null)
|
||||
return;
|
||||
|
||||
Class<?>[] interfaces = clientClass.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
if (ICoreClient.class.isAssignableFrom(interfaces[i])) {
|
||||
Class<? extends ICoreClient> intf = (Class<? extends ICoreClient>) interfaces[i];
|
||||
CoreManager.addClient(intf, client);
|
||||
//logger.info("client(" + client + ") added for " + clientClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> superClass = clientClass.getSuperclass();
|
||||
addClient(client, superClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听所有client声明实现的ICoreClient的接口
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
|
||||
public static void addClientICoreClient(ICoreClient client) {
|
||||
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addClient(client, client.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除对象对某个接口的监听
|
||||
*
|
||||
* @param clientClass
|
||||
* @param client
|
||||
*/
|
||||
public static void removeClient(Class<? extends ICoreClient> clientClass, ICoreClient client) {
|
||||
|
||||
if (clientClass == null || client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<ICoreClient> clientList = clients.get(clientClass);
|
||||
if (clientList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
clientList.remove(client);
|
||||
//MLog.verbose(TAG, "client(" + client + ") removed for " + clientClass.getName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除该对象所有监听接口
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public static void removeClientICoreClient(ICoreClient client) {
|
||||
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<CopyOnWriteArraySet<ICoreClient>> c = clients.values();
|
||||
for (Set<ICoreClient> list : c) {
|
||||
list.remove(client);
|
||||
}
|
||||
|
||||
//MLog.verbose(TAG, "client(" + client + ") removed from all");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 增加Client,支持CoreEvent注解
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public static void addClient(Object client) {
|
||||
//MLog.verbose(TAG_EVENT, "AddClient support CoreEvent : " + client);
|
||||
if (client == null) {
|
||||
MLog.warn(TAG_EVENT, "Don't give me a null client");
|
||||
return;
|
||||
}
|
||||
|
||||
if (client instanceof ICoreClient) {
|
||||
//MLog.verbose(TAG_EVENT, client + " instanceof ICoreClient, add to ICoreClient");
|
||||
addClientICoreClient((ICoreClient) client);
|
||||
}
|
||||
|
||||
Class<?> originalClass = client.getClass();
|
||||
if (originalClass == null) {
|
||||
MLog.warn(TAG_EVENT, "Client.getClass() is null");
|
||||
return;
|
||||
}
|
||||
Method[] methods = originalClass.getMethods();
|
||||
|
||||
for (Method method : methods) {
|
||||
CoreEvent event = method.getAnnotation(CoreEvent.class);
|
||||
if (event != null) {
|
||||
Class<?> clientClass = event.coreClientClass();
|
||||
//MLog.verbose(TAG_EVENT, "Client =" + client + ", event=" + event + ",method=" + method.getName());
|
||||
if (clientClass != null) {
|
||||
addCoreEvents(client, clientClass);
|
||||
addCoreEventMethodsIfNeeded(client, clientClass, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addCoreEvents(Object client, Class<?> clientClass) {
|
||||
CopyOnWriteArraySet<Object> clients = coreEvents.get(clientClass);
|
||||
if (clients == null) {
|
||||
//MLog.verbose(TAG_EVENT, "Clients is null, create new set :" + clientClass);
|
||||
clients = new CopyOnWriteArraySet<>();
|
||||
coreEvents.put(clientClass, clients);
|
||||
}
|
||||
|
||||
clients.add(client);
|
||||
//MLog.verbose(TAG_EVENT, "Clients add client " + client + ",size=" + clients.size());
|
||||
}
|
||||
|
||||
private static void addCoreEventMethodsIfNeeded(Object client, Class<?> clientClass, /*Class<?> originalClass*/Method m) {
|
||||
Map<String, Method> methods = coreEventMethods.get(client);
|
||||
if (methods == null) {
|
||||
//MLog.verbose(TAG_EVENT, "Client " + client + ",Class " + clientClass + " methods null, create new one");
|
||||
methods = new HashMap<String, Method>();
|
||||
coreEventMethods.put(client, methods);
|
||||
}
|
||||
//MLog.verbose(TAG_EVENT, "Client=" + client + ",Class=" + clientClass + ",put method=" + m.getName());
|
||||
methods.put(m.getName(), m);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 移除该对象所有监听接口,支持CoreEvent
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public static void removeClient(Object client) {
|
||||
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (client instanceof ICoreClient) {
|
||||
/*if (isDebugSvc())
|
||||
MLog.verbose(TAG_EVENT, "Client is ICoreClient, remove core client method");*/
|
||||
removeClientICoreClient((ICoreClient) client);
|
||||
}
|
||||
|
||||
Collection<CopyOnWriteArraySet<Object>> c = coreEvents.values();
|
||||
for (CopyOnWriteArraySet<Object> events : c) {
|
||||
events.remove(client);
|
||||
}
|
||||
|
||||
coreEventMethods.remove(client);
|
||||
} catch (Throwable throwable) {
|
||||
MLog.error("CoreManager", "removeClient error! " + throwable);
|
||||
}
|
||||
|
||||
//MLog.verbose(TAG_EVENT, "client(" + client + ") removed from all");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回监听该接口的对象列表
|
||||
*
|
||||
* @param clientClass
|
||||
* @return
|
||||
*/
|
||||
public static Set<ICoreClient> getClients(Class<? extends ICoreClient> clientClass) {
|
||||
|
||||
if (clientClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CopyOnWriteArraySet<ICoreClient> clientList = clients.get(clientClass);
|
||||
return clientList;
|
||||
}
|
||||
|
||||
public interface ICallBack {
|
||||
void onCall(ICoreClient client);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行回调接口
|
||||
*
|
||||
* @param clientClass
|
||||
* @param callBack
|
||||
*/
|
||||
public static void notifyClients(Class<? extends ICoreClient> clientClass, ICallBack callBack) {
|
||||
if (clientClass == null || callBack == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<ICoreClient> clientList = CoreManager.getClients(clientClass);
|
||||
if (clientList == null) {
|
||||
if (clientList == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (ICoreClient client : clientList) {
|
||||
callBack.onCall(client);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
MLog.error(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调所有监听了该接口的对象。methodName为回调的方法名
|
||||
* 注意:所有用addClient和addEventListener注册了此接口的对象都会被回调
|
||||
* 注意:methodName所指定函数的参数列表个数必须匹配。目前没有对参数类型严格检查,使用时要注意
|
||||
*
|
||||
* @param clientClass
|
||||
* @param methodName
|
||||
* @param args
|
||||
*/
|
||||
public static void notifyClients(Class<? extends ICoreClient> clientClass, String methodName, Object... args) {
|
||||
notifyClientsCoreEvents(clientClass, methodName, args);
|
||||
if (clientClass == null || methodName == null || methodName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<ICoreClient> clientList = CoreManager.getClients(clientClass);
|
||||
if (clientList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Map<String, Method> methods = clientMethods.get(clientClass);
|
||||
Method method = methods.get(methodName);
|
||||
|
||||
if (method == null) {
|
||||
MLog.error(TAG, "cannot find client method " + methodName + " for args[" + args.length + "]: " + Arrays.toString(args));
|
||||
return;
|
||||
} else if (method.getParameterTypes() == null) {
|
||||
MLog.error(TAG, "cannot find client method param:" + method.getParameterTypes() + " for args[" + args.length + "]: " + Arrays.toString(args));
|
||||
return;
|
||||
} else if (method.getParameterTypes().length != args.length) {
|
||||
MLog.error(TAG, "method " + methodName + " param number not matched: method(" + method.getParameterTypes().length + "), args(" + args.length + ")");
|
||||
return;
|
||||
}
|
||||
for (Object c : clientList) {
|
||||
try {
|
||||
method.invoke(c, args);
|
||||
} catch (Throwable e) {
|
||||
MLog.error(TAG, "Notify clients method invoke error.", e);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
MLog.error(TAG, "Notify clients error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 广播CoreEvent注解事件
|
||||
*
|
||||
* @param clientClass
|
||||
* @param methodName
|
||||
* @param args
|
||||
*/
|
||||
public static void notifyClientsCoreEvents(Class<? extends ICoreClient> clientClass, String methodName, Object... args) {
|
||||
|
||||
if (clientClass == null || methodName == null || methodName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Object> clients = coreEvents.get(clientClass);
|
||||
|
||||
if (clients == null) {
|
||||
MLog.debug(TAG_EVENT, "core clients is null clientClz:%s", clientClass.getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (Object c : clients) {
|
||||
Map<String, Method> methods = coreEventMethods.get(c);
|
||||
if (methods == null) {
|
||||
continue;
|
||||
}
|
||||
Method method = methods.get(methodName);
|
||||
Class<?>[] types = null;
|
||||
if (method != null) {
|
||||
types = method.getParameterTypes();//减少创建小对象,减少timeout崩溃
|
||||
}
|
||||
|
||||
if (method == null) {
|
||||
continue;
|
||||
}else if (types == null) {
|
||||
MLog.error(TAG_EVENT, "Can't find " + c + " has method param null for args[" + args.length + "]: " + args);
|
||||
continue;
|
||||
} else if (types.length != args.length) {
|
||||
MLog.error(TAG_EVENT, "Can't find " + c + " has Method " + methodName +
|
||||
" param number not matched: method(" + types.length +
|
||||
"), args(" + args.length + ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
method.invoke(c, args);
|
||||
} catch (Throwable e) {
|
||||
MLog.error(TAG_EVENT, "Notify core events method invoke error class=" + clientClass
|
||||
+ ",method=" + methodName
|
||||
+ ",args=" + args, e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
MLog.error(TAG_EVENT, "Notify core events error class=" + clientClass + ",method=" + methodName
|
||||
+ ",args=" + args, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T extends IBaseCore> T getCore(Class<T> cls) {
|
||||
return CoreFactory.getCore(cls);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 所有的core接口必须继承此接口
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*
|
||||
*/
|
||||
public interface IBaseCore {
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 所有的core client接口必须继承于此接口
|
||||
*/
|
||||
package com.chwl.library.coremanager;
|
||||
|
||||
/**
|
||||
* @author daixiang
|
||||
*
|
||||
*/
|
||||
public interface ICoreClient {
|
||||
|
||||
}
|
121
library/src/main/java/com/chwl/library/error/CrashCat.java
Normal file
121
library/src/main/java/com/chwl/library/error/CrashCat.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package com.chwl.library.error;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
//xxx 崩溃日志拦截
|
||||
public class CrashCat implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static CrashCat crashCat;
|
||||
private Context mContext;
|
||||
private Thread.UncaughtExceptionHandler mDefaultHandler;
|
||||
private static String DEVICE_INFO="";
|
||||
private File outPutDir;
|
||||
private FileOutputStream fileOutputStream;
|
||||
private BufferedOutputStream bufferedOutputStream;
|
||||
private static String FILE_NAME = "";
|
||||
private Intent intent;
|
||||
private PackageManager packageManager;
|
||||
private PackageInfo packageInfo;
|
||||
|
||||
private CrashCat(Context context, String filePath, String fileName){
|
||||
init(context,filePath,fileName);
|
||||
}
|
||||
|
||||
public static CrashCat getInstance(Context context, String filePath, String fileName){
|
||||
crashCat = new CrashCat(context,filePath,fileName);
|
||||
return crashCat;
|
||||
}
|
||||
|
||||
private void init(Context context, String filePath, String fileName){
|
||||
this.mContext = context;
|
||||
this.FILE_NAME = fileName;
|
||||
try {
|
||||
packageManager = mContext.getPackageManager();
|
||||
packageInfo = packageManager.getPackageInfo(mContext.getPackageName(),0);
|
||||
intent = packageManager.getLaunchIntentForPackage(mContext.getPackageName());
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
} catch (Exception e) {
|
||||
writeLog(e.toString());
|
||||
intent = null;
|
||||
}
|
||||
|
||||
outPutDir = context.getExternalFilesDir(null);
|
||||
if (outPutDir != null){
|
||||
if (!outPutDir.exists()) {
|
||||
outPutDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("DeviceID="+ Build.ID+"\n");
|
||||
sb.append("AndroidApi="+ Build.VERSION.SDK_INT+"\n");
|
||||
sb.append("AndroidVersion="+ Build.VERSION.RELEASE+"\n");
|
||||
sb.append("Brand="+ Build.BRAND+"\n");
|
||||
sb.append("ManuFacture="+ Build.MANUFACTURER+"\n");
|
||||
sb.append("Model="+ Build.MODEL+"\n");
|
||||
sb.append("PackageName="+mContext.getPackageName()+"\n");
|
||||
sb.append("CurrentVersionName="+packageInfo.versionName+"\n");
|
||||
DEVICE_INFO = sb.toString();
|
||||
// writeLog("Application Start");
|
||||
}
|
||||
|
||||
public void start(){
|
||||
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
}
|
||||
|
||||
private void writeLog(String log){
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
SimpleDateFormat fileTime = new SimpleDateFormat("yyyy-MM-dd--HH-mm-ss");
|
||||
log = "----------"+simpleDateFormat.format(new Date(System.currentTimeMillis())).toString()+"----------"+"\n"+log+"\n";
|
||||
try {
|
||||
|
||||
File outPutFile = new File(outPutDir, "error-"+fileTime.format(new Date(System.currentTimeMillis()))+".txt");
|
||||
fileOutputStream = new FileOutputStream(outPutFile,true);
|
||||
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
|
||||
bufferedOutputStream.write(log.getBytes());
|
||||
bufferedOutputStream.flush();
|
||||
fileOutputStream.close();
|
||||
bufferedOutputStream.close();
|
||||
} catch (Exception e) {
|
||||
Log.e("IO Exception",e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void handlerException(String exception) {
|
||||
if (exception !=null){
|
||||
try{
|
||||
writeLog(DEVICE_INFO+exception.toString());
|
||||
}finally {
|
||||
try{
|
||||
mContext.startActivity(intent);
|
||||
System.exit(1);
|
||||
}catch (Exception e){
|
||||
Log.e("App can not restart",e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
StackTraceElement[] stackTraceElements = e.getStackTrace();
|
||||
StringBuffer sb = new StringBuffer(e.toString()+"\n");
|
||||
for (int i=0,size = stackTraceElements.length;i<size;i++){
|
||||
sb.append(stackTraceElements[i].toString()+"\n");
|
||||
}
|
||||
Log.e("errororor",sb.toString());
|
||||
handlerException(sb.toString());
|
||||
}
|
||||
}
|
@@ -0,0 +1,219 @@
|
||||
package com.chwl.library.language
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.LocaleList
|
||||
import com.chwl.library.common.util.SPUtils
|
||||
import com.example.lib_utils.log.ILog
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by Max on 2022/3/28 10:28
|
||||
* Desc:语言助手
|
||||
*/
|
||||
object LanguageHelper : ILog {
|
||||
const val ZH = "zh"
|
||||
const val EN = "en"
|
||||
const val AR = "ar"
|
||||
const val TR = "tr"
|
||||
|
||||
private var currentLocale: Locale? = null
|
||||
|
||||
/**
|
||||
* 系统语言
|
||||
*/
|
||||
fun getSystemLanguage(): Locale {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
LocaleList.getDefault()[0]
|
||||
} else {
|
||||
Locale.getDefault()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前语言
|
||||
*/
|
||||
fun getCurrentLanguage(): Locale {
|
||||
var locale = currentLocale
|
||||
if (locale != null) {
|
||||
return locale
|
||||
}
|
||||
val language = SPUtils.getString("language", "")
|
||||
locale = buildLocaleByLanguageTag(
|
||||
language,
|
||||
limitSupportedLocale(getSystemLanguage(), Locale.ENGLISH)
|
||||
)
|
||||
currentLocale = locale
|
||||
return locale
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言类型(四端一致的语言类型)
|
||||
*/
|
||||
fun getCurrentLanguageType(): String {
|
||||
val locale = getCurrentLanguage()
|
||||
return if (locale == Locale.ENGLISH) {
|
||||
EN
|
||||
} else if (locale == Locale.TRADITIONAL_CHINESE) {
|
||||
ZH
|
||||
} else if (locale.language.equals("ar", true)) {
|
||||
return AR
|
||||
} else if (locale.language.equals("tr", true)) {
|
||||
return TR
|
||||
} else {
|
||||
EN
|
||||
}
|
||||
}
|
||||
|
||||
fun getH5GameLeaderccLan(): String {
|
||||
val locale = getCurrentLanguage()
|
||||
return if (locale == Locale.ENGLISH) {
|
||||
"en-US"
|
||||
} else if (locale == Locale.TRADITIONAL_CHINESE) {
|
||||
"zh-TW"
|
||||
} else if (locale.language.equals("ar", true)) {
|
||||
return "ar-EG"
|
||||
} else if (locale.language.equals("tr", true)) {
|
||||
return "tr-TR"
|
||||
} else {
|
||||
"en-Us"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Locale
|
||||
* @param language (四端一致的类型)
|
||||
*/
|
||||
fun getLocaleByLanguageType(language: String?): Locale? {
|
||||
return when (language) {
|
||||
ZH -> {
|
||||
Locale.TRADITIONAL_CHINESE
|
||||
}
|
||||
|
||||
EN -> {
|
||||
Locale.ENGLISH
|
||||
}
|
||||
|
||||
AR -> {
|
||||
Locale("ar")
|
||||
}
|
||||
TR -> {
|
||||
Locale("tr")
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改语言配置
|
||||
*/
|
||||
fun changeLanguageConfig(locale: Locale) {
|
||||
SPUtils.putString("language", locale.toLanguageTag())
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换语言
|
||||
*/
|
||||
fun changeLanguage(context: Context, locale: Locale): Context {
|
||||
changeResources(context, locale)
|
||||
currentLocale = locale
|
||||
Locale.setDefault(locale)
|
||||
val resources: Resources = context.resources
|
||||
val metrics = resources.displayMetrics
|
||||
val configuration = resources.configuration
|
||||
configuration.setLayoutDirection(locale)
|
||||
return when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
|
||||
configuration.setLocale(locale)
|
||||
configuration.setLocales(LocaleList(locale))
|
||||
resources.updateConfiguration(configuration, metrics)
|
||||
context.createConfigurationContext(configuration)
|
||||
}
|
||||
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 -> {
|
||||
configuration.setLocale(locale)
|
||||
resources.updateConfiguration(configuration, metrics)
|
||||
context
|
||||
}
|
||||
|
||||
else -> {
|
||||
configuration.locale = locale
|
||||
resources.updateConfiguration(configuration, metrics)
|
||||
context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变资源
|
||||
*/
|
||||
private fun changeResources(context: Context, locale: Locale): Resources? {
|
||||
try {
|
||||
val res = context.resources ?: return null
|
||||
val configuration = Configuration(res.configuration)
|
||||
configuration.setLocale(locale)
|
||||
return Resources(res.assets, res.displayMetrics, configuration)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置Context多语言
|
||||
*/
|
||||
fun wrapContext(context: Context?): Context? {
|
||||
if (context == null) return null
|
||||
val currentLanguage = getCurrentLanguage()
|
||||
return changeLanguage(context, currentLanguage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据国家语言TAG构建Locale
|
||||
*/
|
||||
private fun buildLocaleByLanguageTag(languageTag: String, defaultLocale: Locale): Locale {
|
||||
return try {
|
||||
if (languageTag.isEmpty()) {
|
||||
return defaultLocale
|
||||
}
|
||||
limitSupportedLocale(Locale.forLanguageTag(languageTag), defaultLocale)
|
||||
} catch (e: Exception) {
|
||||
defaultLocale
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制为支持的语言
|
||||
*/
|
||||
private fun limitSupportedLocale(locale: Locale, defaultLocale: Locale): Locale {
|
||||
logI(
|
||||
"limitSupportedLocale() language:${locale.language} country:${locale.country} toLanguageTag:${locale.toLanguageTag()}",
|
||||
filePrinter = true
|
||||
)
|
||||
when (locale.language) {
|
||||
AR -> {
|
||||
return Locale("ar")
|
||||
}
|
||||
TR -> {
|
||||
return Locale("tr")
|
||||
}
|
||||
|
||||
EN -> {
|
||||
return Locale.ENGLISH
|
||||
}
|
||||
|
||||
ZH -> {
|
||||
return Locale.TRADITIONAL_CHINESE
|
||||
}
|
||||
|
||||
else -> {
|
||||
return defaultLocale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,606 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Animation.AnimationListener;
|
||||
import android.view.animation.Transformation;
|
||||
import android.widget.BaseExpandableListAdapter;
|
||||
import android.widget.ExpandableListAdapter;
|
||||
import android.widget.ExpandableListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class defines an ExpandableListView which supports animations for
|
||||
* collapsing and expanding groups.
|
||||
*/
|
||||
public class AnimatedExpandableListView extends ExpandableListView {
|
||||
/*
|
||||
* A detailed explanation for how this class works:
|
||||
*
|
||||
* Animating the ExpandableListView was no easy task. The way that this
|
||||
* class does it is by exploiting how an ExpandableListView works.
|
||||
*
|
||||
* Normally when {@link ExpandableListView#collapseGroup(int)} or
|
||||
* {@link ExpandableListView#expandGroup(int)} is called, the view toggles
|
||||
* the flag for a group and calls notifyDataSetChanged to cause the ListView
|
||||
* to refresh all of it's view. This time however, depending on whether a
|
||||
* group is expanded or collapsed, certain childViews will either be ignored
|
||||
* or added to the list.
|
||||
*
|
||||
* Knowing this, we can come up with a way to animate our views. For
|
||||
* instance for group expansion, we tell the adapter to animate the
|
||||
* children of a certain group. We then expand the group which causes the
|
||||
* ExpandableListView to refresh all views on screen. The way that
|
||||
* ExpandableListView does this is by calling getView() in the adapter.
|
||||
* However since the adapter knows that we are animating a certain group,
|
||||
* instead of returning the real views for the children of the group being
|
||||
* animated, it will return a fake dummy view. This dummy view will then
|
||||
* draw the real child views within it's dispatchDraw function. The reason
|
||||
* we do this is so that we can animate all of it's children by simply
|
||||
* animating the dummy view. After we complete the animation, we tell the
|
||||
* adapter to stop animating the group and call notifyDataSetChanged. Now
|
||||
* the ExpandableListView is forced to refresh it's views again, except this
|
||||
* time, it will get the real views for the expanded group.
|
||||
*
|
||||
* So, to list it all out, when {@link #expandGroupWithAnimation(int)} is
|
||||
* called the following happens:
|
||||
*
|
||||
* 1. The ExpandableListView tells the adapter to animate a certain group.
|
||||
* 2. The ExpandableListView calls expandGroup.
|
||||
* 3. ExpandGroup calls notifyDataSetChanged.
|
||||
* 4. As an result, getChildView is called for expanding group.
|
||||
* 5. Since the adapter is in "animating mode", it will return a dummy view.
|
||||
* 6. This dummy view draws the actual children of the expanding group.
|
||||
* 7. This dummy view's height is animated from 0 to it's expanded height.
|
||||
* 8. Once the animation completes, the adapter is notified to stop
|
||||
* animating the group and notifyDataSetChanged is called again.
|
||||
* 9. This forces the ExpandableListView to refresh all of it's views again.
|
||||
* 10.This time when getChildView is called, it will return the actual
|
||||
* child views.
|
||||
*
|
||||
* For animating the collapse of a group is a bit more difficult since we
|
||||
* can't call collapseGroup from the start as it would just ignore the
|
||||
* child items, giving up no chance to do any sort of animation. Instead
|
||||
* what we have to do is play the animation first and call collapseGroup
|
||||
* after the animation is done.
|
||||
*
|
||||
* So, to list it all out, when {@link #collapseGroupWithAnimation(int)} is
|
||||
* called the following happens:
|
||||
*
|
||||
* 1. The ExpandableListView tells the adapter to animate a certain group.
|
||||
* 2. The ExpandableListView calls notifyDataSetChanged.
|
||||
* 3. As an result, getChildView is called for expanding group.
|
||||
* 4. Since the adapter is in "animating mode", it will return a dummy view.
|
||||
* 5. This dummy view draws the actual children of the expanding group.
|
||||
* 6. This dummy view's height is animated from it's current height to 0.
|
||||
* 7. Once the animation completes, the adapter is notified to stop
|
||||
* animating the group and notifyDataSetChanged is called again.
|
||||
* 8. collapseGroup is finally called.
|
||||
* 9. This forces the ExpandableListView to refresh all of it's views again.
|
||||
* 10.This time when the ListView will not get any of the child views for
|
||||
* the collapsed group.
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = AnimatedExpandableListAdapter.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* The duration of the expand/collapse animations
|
||||
*/
|
||||
private static final int ANIMATION_DURATION = 300;
|
||||
|
||||
private AnimatedExpandableListAdapter adapter;
|
||||
|
||||
public AnimatedExpandableListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AnimatedExpandableListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public AnimatedExpandableListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ExpandableListView#setAdapter(ExpandableListAdapter)
|
||||
*/
|
||||
public void setAdapter(ExpandableListAdapter adapter) {
|
||||
super.setAdapter(adapter);
|
||||
|
||||
// Make sure that the adapter extends AnimatedExpandableListAdapter
|
||||
if (adapter instanceof AnimatedExpandableListAdapter) {
|
||||
this.adapter = (AnimatedExpandableListAdapter) adapter;
|
||||
this.adapter.setParent(this);
|
||||
} else {
|
||||
throw new ClassCastException(adapter.toString() + " must implement AnimatedExpandableListAdapter");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the given group with an animation.
|
||||
*
|
||||
* @param groupPos The position of the group to expand
|
||||
* @return Returns true if the group was expanded. False if the group was
|
||||
* already expanded.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public boolean expandGroupWithAnimation(int groupPos) {
|
||||
boolean lastGroup = groupPos == adapter.getGroupCount() - 1;
|
||||
if (lastGroup && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return expandGroup(groupPos, true);
|
||||
}
|
||||
|
||||
int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
|
||||
if (groupFlatPos != -1) {
|
||||
int childIndex = groupFlatPos - getFirstVisiblePosition();
|
||||
if (childIndex < getChildCount()) {
|
||||
// Get the view for the group is it is on screen...
|
||||
View v = getChildAt(childIndex);
|
||||
if (v.getBottom() >= getBottom()) {
|
||||
// If the user is not going to be able to see the animation
|
||||
// we just expand the group without an animation.
|
||||
// This resolves the case where getChildView will not be
|
||||
// called if the children of the group is not on screen
|
||||
|
||||
// We need to notify the adapter that the group was expanded
|
||||
// without it's knowledge
|
||||
adapter.notifyGroupExpanded(groupPos);
|
||||
return expandGroup(groupPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let the adapter know that we are starting the animation...
|
||||
adapter.startExpandAnimation(groupPos, 0);
|
||||
// Finally call expandGroup (note that expandGroup will call
|
||||
// notifyDataSetChanged so we don't need to)
|
||||
return expandGroup(groupPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses the given group with an animation.
|
||||
*
|
||||
* @param groupPos The position of the group to collapse
|
||||
* @return Returns true if the group was collapsed. False if the group was
|
||||
* already collapsed.
|
||||
*/
|
||||
public boolean collapseGroupWithAnimation(int groupPos) {
|
||||
int groupFlatPos = getFlatListPosition(getPackedPositionForGroup(groupPos));
|
||||
if (groupFlatPos != -1) {
|
||||
int childIndex = groupFlatPos - getFirstVisiblePosition();
|
||||
if (childIndex >= 0 && childIndex < getChildCount()) {
|
||||
// Get the view for the group is it is on screen...
|
||||
View v = getChildAt(childIndex);
|
||||
if (v.getBottom() >= getBottom()) {
|
||||
// If the user is not going to be able to see the animation
|
||||
// we just collapse the group without an animation.
|
||||
// This resolves the case where getChildView will not be
|
||||
// called if the children of the group is not on screen
|
||||
return collapseGroup(groupPos);
|
||||
}
|
||||
} else {
|
||||
// If the group is offscreen, we can just collapse it without an
|
||||
// animation...
|
||||
return collapseGroup(groupPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the position of the firstChild visible from the top of the screen
|
||||
long packedPos = getExpandableListPosition(getFirstVisiblePosition());
|
||||
int firstChildPos = getPackedPositionChild(packedPos);
|
||||
int firstGroupPos = getPackedPositionGroup(packedPos);
|
||||
|
||||
// If the first visible view on the screen is a child view AND it's a
|
||||
// child of the group we are trying to collapse, then set that
|
||||
// as the first child position of the group... see
|
||||
// {@link #startCollapseAnimation(int, int)} for why this is necessary
|
||||
firstChildPos = firstChildPos == -1 || firstGroupPos != groupPos ? 0 : firstChildPos;
|
||||
|
||||
// Let the adapter know that we are going to start animating the
|
||||
// collapse animation.
|
||||
adapter.startCollapseAnimation(groupPos, firstChildPos);
|
||||
|
||||
// Force the listview to refresh it's views
|
||||
adapter.notifyDataSetChanged();
|
||||
return isGroupExpanded(groupPos);
|
||||
}
|
||||
|
||||
private int getAnimationDuration() {
|
||||
return ANIMATION_DURATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for holding information regarding the group.
|
||||
*/
|
||||
private static class GroupInfo {
|
||||
boolean animating = false;
|
||||
boolean expanding = false;
|
||||
int firstChildPosition;
|
||||
|
||||
/**
|
||||
* This variable contains the last known height value of the dummy view.
|
||||
* We save this information so that if the user collapses a group
|
||||
* before it fully expands, the collapse animation will start from the
|
||||
* CURRENT height of the dummy view and not from the full expanded
|
||||
* height.
|
||||
*/
|
||||
int dummyHeight = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A specialized adapter for use with the AnimatedExpandableListView. All
|
||||
* adapters used with AnimatedExpandableListView MUST extend this class.
|
||||
*/
|
||||
public static abstract class AnimatedExpandableListAdapter extends BaseExpandableListAdapter {
|
||||
private SparseArray<GroupInfo> groupInfo = new SparseArray<GroupInfo>();
|
||||
private AnimatedExpandableListView parent;
|
||||
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_EXPANDING = 1;
|
||||
private static final int STATE_COLLAPSING = 2;
|
||||
|
||||
private void setParent(AnimatedExpandableListView parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public int getRealChildType(int groupPosition, int childPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getRealChildTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public abstract View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent);
|
||||
|
||||
public abstract int getRealChildrenCount(int groupPosition);
|
||||
|
||||
private GroupInfo getGroupInfo(int groupPosition) {
|
||||
GroupInfo info = groupInfo.get(groupPosition);
|
||||
if (info == null) {
|
||||
info = new GroupInfo();
|
||||
groupInfo.put(groupPosition, info);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
public void notifyGroupExpanded(int groupPosition) {
|
||||
GroupInfo info = getGroupInfo(groupPosition);
|
||||
info.dummyHeight = -1;
|
||||
}
|
||||
|
||||
private void startExpandAnimation(int groupPosition, int firstChildPosition) {
|
||||
GroupInfo info = getGroupInfo(groupPosition);
|
||||
info.animating = true;
|
||||
info.firstChildPosition = firstChildPosition;
|
||||
info.expanding = true;
|
||||
}
|
||||
|
||||
private void startCollapseAnimation(int groupPosition, int firstChildPosition) {
|
||||
GroupInfo info = getGroupInfo(groupPosition);
|
||||
info.animating = true;
|
||||
info.firstChildPosition = firstChildPosition;
|
||||
info.expanding = false;
|
||||
}
|
||||
|
||||
private void stopAnimation(int groupPosition) {
|
||||
GroupInfo info = getGroupInfo(groupPosition);
|
||||
info.animating = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override {@link #getRealChildType(int, int)} instead.
|
||||
*/
|
||||
@Override
|
||||
public final int getChildType(int groupPosition, int childPosition) {
|
||||
GroupInfo info = getGroupInfo(groupPosition);
|
||||
if (info.animating) {
|
||||
// If we are animating this group, then all of it's children
|
||||
// are going to be dummy views which we will say is type 0.
|
||||
return 0;
|
||||
} else {
|
||||
// If we are not animating this group, then we will add 1 to
|
||||
// the type it has so that no type id conflicts will occur
|
||||
// unless getRealChildType() returns MAX_INT
|
||||
return getRealChildType(groupPosition, childPosition) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override {@link #getRealChildTypeCount()} instead.
|
||||
*/
|
||||
@Override
|
||||
public final int getChildTypeCount() {
|
||||
// Return 1 more than the childTypeCount to account for DummyView
|
||||
return getRealChildTypeCount() + 1;
|
||||
}
|
||||
|
||||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override {@link #getChildView(int, int, boolean, View, ViewGroup)} instead.
|
||||
*/
|
||||
@Override
|
||||
public final View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
|
||||
final GroupInfo info = getGroupInfo(groupPosition);
|
||||
|
||||
if (info.animating) {
|
||||
// If this group is animating, return the a DummyView...
|
||||
if (convertView instanceof DummyView == false) {
|
||||
convertView = new DummyView(parent.getContext());
|
||||
convertView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 0));
|
||||
}
|
||||
|
||||
if (childPosition < info.firstChildPosition) {
|
||||
// The reason why we do this is to support the collapse
|
||||
// this group when the group view is not visible but the
|
||||
// children of this group are. When notifyDataSetChanged
|
||||
// is called, the ExpandableListView tries to keep the
|
||||
// list position the same by saving the first visible item
|
||||
// and jumping back to that item after the views have been
|
||||
// refreshed. Now the problem is, if a group has 2 items
|
||||
// and the first visible item is the 2nd child of the group
|
||||
// and this group is collapsed, then the dummy view will be
|
||||
// used for the group. But now the group only has 1 item
|
||||
// which is the dummy view, thus when the ListView is trying
|
||||
// to restore the scroll position, it will try to jump to
|
||||
// the second item of the group. But this group no longer
|
||||
// has a second item, so it is forced to jump to the next
|
||||
// group. This will cause a very ugly visual glitch. So
|
||||
// the way that we counteract this is by creating as many
|
||||
// dummy views as we need to maintain the scroll position
|
||||
// of the ListView after notifyDataSetChanged has been
|
||||
// called.
|
||||
convertView.getLayoutParams().height = 0;
|
||||
return convertView;
|
||||
}
|
||||
|
||||
final ExpandableListView listView = (ExpandableListView) parent;
|
||||
|
||||
final DummyView dummyView = (DummyView) convertView;
|
||||
|
||||
// Clear the views that the dummy view draws.
|
||||
dummyView.clearViews();
|
||||
|
||||
// Set the style of the divider
|
||||
dummyView.setDivider(listView.getDivider(), parent.getMeasuredWidth(), listView.getDividerHeight());
|
||||
|
||||
// Make measure specs to measure child views
|
||||
final int measureSpecW = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY);
|
||||
final int measureSpecH = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||
|
||||
int totalHeight = 0;
|
||||
int clipHeight = parent.getHeight();
|
||||
|
||||
final int len = getRealChildrenCount(groupPosition);
|
||||
for (int i = info.firstChildPosition; i < len; i++) {
|
||||
View childView = getRealChildView(groupPosition, i, (i == len - 1), null, parent);
|
||||
|
||||
LayoutParams p = (LayoutParams) childView.getLayoutParams();
|
||||
if (p == null) {
|
||||
p = (LayoutParams) generateDefaultLayoutParams();
|
||||
childView.setLayoutParams(p);
|
||||
}
|
||||
|
||||
int lpHeight = p.height;
|
||||
|
||||
int childHeightSpec;
|
||||
if (lpHeight > 0) {
|
||||
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
|
||||
} else {
|
||||
childHeightSpec = measureSpecH;
|
||||
}
|
||||
|
||||
childView.measure(measureSpecW, childHeightSpec);
|
||||
totalHeight += childView.getMeasuredHeight();
|
||||
|
||||
if (totalHeight < clipHeight) {
|
||||
// we only need to draw enough views to fool the user...
|
||||
dummyView.addFakeView(childView);
|
||||
} else {
|
||||
dummyView.addFakeView(childView);
|
||||
|
||||
// if this group has too many views, we don't want to
|
||||
// calculate the height of everything... just do a light
|
||||
// approximation and break
|
||||
int averageHeight = totalHeight / (i + 1);
|
||||
totalHeight += (len - i - 1) * averageHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Object o;
|
||||
int state = (o = dummyView.getTag()) == null ? STATE_IDLE : (Integer) o;
|
||||
|
||||
if (info.expanding && state != STATE_EXPANDING) {
|
||||
ExpandAnimation ani = new ExpandAnimation(dummyView, 0, totalHeight, info);
|
||||
ani.setDuration(this.parent.getAnimationDuration());
|
||||
ani.setAnimationListener(new AnimationListener() {
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
stopAnimation(groupPosition);
|
||||
notifyDataSetChanged();
|
||||
dummyView.setTag(STATE_IDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
});
|
||||
dummyView.startAnimation(ani);
|
||||
dummyView.setTag(STATE_EXPANDING);
|
||||
} else if (!info.expanding && state != STATE_COLLAPSING) {
|
||||
if (info.dummyHeight == -1) {
|
||||
info.dummyHeight = totalHeight;
|
||||
}
|
||||
|
||||
ExpandAnimation ani = new ExpandAnimation(dummyView, info.dummyHeight, 0, info);
|
||||
ani.setDuration(this.parent.getAnimationDuration());
|
||||
ani.setAnimationListener(new AnimationListener() {
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
stopAnimation(groupPosition);
|
||||
listView.collapseGroup(groupPosition);
|
||||
notifyDataSetChanged();
|
||||
info.dummyHeight = -1;
|
||||
dummyView.setTag(STATE_IDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
});
|
||||
dummyView.startAnimation(ani);
|
||||
dummyView.setTag(STATE_COLLAPSING);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
} else {
|
||||
return getRealChildView(groupPosition, childPosition, isLastChild, convertView, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getChildrenCount(int groupPosition) {
|
||||
GroupInfo info = getGroupInfo(groupPosition);
|
||||
if (info.animating) {
|
||||
return info.firstChildPosition + 1;
|
||||
} else {
|
||||
return getRealChildrenCount(groupPosition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DummyView extends View {
|
||||
private List<View> views = new ArrayList<View>();
|
||||
private Drawable divider;
|
||||
private int dividerWidth;
|
||||
private int dividerHeight;
|
||||
|
||||
public DummyView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setDivider(Drawable divider, int dividerWidth, int dividerHeight) {
|
||||
if (divider != null) {
|
||||
this.divider = divider;
|
||||
this.dividerWidth = dividerWidth;
|
||||
this.dividerHeight = dividerHeight;
|
||||
|
||||
divider.setBounds(0, 0, dividerWidth, dividerHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a view for the DummyView to draw.
|
||||
*
|
||||
* @param childView View to draw
|
||||
*/
|
||||
public void addFakeView(View childView) {
|
||||
childView.layout(0, 0, getWidth(), childView.getMeasuredHeight());
|
||||
views.add(childView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
final int len = views.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
View v = views.get(i);
|
||||
v.layout(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
|
||||
public void clearViews() {
|
||||
views.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
canvas.save();
|
||||
if (divider != null) {
|
||||
divider.setBounds(0, 0, dividerWidth, dividerHeight);
|
||||
}
|
||||
|
||||
final int len = views.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
View v = views.get(i);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(0, 0, getWidth(), v.getMeasuredHeight());
|
||||
v.draw(canvas);
|
||||
canvas.restore();
|
||||
|
||||
if (divider != null) {
|
||||
divider.draw(canvas);
|
||||
canvas.translate(0, dividerHeight);
|
||||
}
|
||||
|
||||
canvas.translate(0, v.getMeasuredHeight());
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExpandAnimation extends Animation {
|
||||
private int baseHeight;
|
||||
private int delta;
|
||||
private View view;
|
||||
private GroupInfo groupInfo;
|
||||
|
||||
private ExpandAnimation(View v, int startHeight, int endHeight, GroupInfo info) {
|
||||
baseHeight = startHeight;
|
||||
delta = endHeight - startHeight;
|
||||
view = v;
|
||||
groupInfo = info;
|
||||
|
||||
view.getLayoutParams().height = startHeight;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
super.applyTransformation(interpolatedTime, t);
|
||||
if (interpolatedTime < 1.0f) {
|
||||
int val = baseHeight + (int) (delta * interpolatedTime);
|
||||
view.getLayoutParams().height = val;
|
||||
groupInfo.dummyHeight = val;
|
||||
view.requestLayout();
|
||||
} else {
|
||||
int val = baseHeight + delta;
|
||||
view.getLayoutParams().height = val;
|
||||
groupInfo.dummyHeight = val;
|
||||
view.requestLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/12.
|
||||
* Modified by wjc133 on 2015/8/27.
|
||||
*/
|
||||
public class ArrayListAdapter extends BaseListAdapter {
|
||||
|
||||
private boolean mNotifyOnChange = true;
|
||||
private final Object mLock = new Object();
|
||||
private List<ListItem> items = new ArrayList<>();
|
||||
|
||||
|
||||
public void addItem(ListItem item) {
|
||||
synchronized (mLock) {
|
||||
items.add(item);
|
||||
}
|
||||
if (mNotifyOnChange) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(List<ListItem> items) {
|
||||
synchronized (mLock) {
|
||||
this.items.addAll(items);
|
||||
}
|
||||
if (mNotifyOnChange) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(ListItem... items) {
|
||||
synchronized (mLock) {
|
||||
Collections.addAll(this.items, items);
|
||||
}
|
||||
if (mNotifyOnChange) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void insert(ListItem item, int index) {
|
||||
synchronized (mLock) {
|
||||
items.add(index, item);
|
||||
}
|
||||
if (mNotifyOnChange) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void remove(ListItem object) {
|
||||
synchronized (mLock) {
|
||||
if (items.contains(object)) {
|
||||
items.remove(object);
|
||||
}
|
||||
}
|
||||
if (mNotifyOnChange) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
synchronized (mLock) {
|
||||
items.clear();
|
||||
}
|
||||
if (mNotifyOnChange) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetChanged() {
|
||||
super.notifyDataSetChanged();
|
||||
mNotifyOnChange = true;
|
||||
}
|
||||
|
||||
public void setNotifyOnChange(boolean notifyOnChange) {
|
||||
mNotifyOnChange = notifyOnChange;
|
||||
}
|
||||
|
||||
public ListItem getItem(int position) {
|
||||
if (position >= 0 && position < items.size()) {
|
||||
return items.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected List<ListItem> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/11.
|
||||
*/
|
||||
public abstract class BaseAnimatedExpandableListAdapter extends AnimatedExpandableListView.AnimatedExpandableListAdapter {
|
||||
|
||||
@Override
|
||||
public abstract int getGroupCount();
|
||||
|
||||
@Override
|
||||
public abstract GroupItem getGroup(int groupPosition);
|
||||
|
||||
@Override
|
||||
public ListItem getChild(int groupPosition, int childPosition) {
|
||||
GroupItem groupItem = getGroup(groupPosition);
|
||||
if (null != groupItem.getChildItems()) {
|
||||
return groupItem.getChildItems().get(childPosition);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getChildId(int groupPosition, int childPosition) {
|
||||
return childPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
|
||||
// Log.d("BaseAnimatedExpandableListAdapter:", "getRealChildView, groupPosition:" + groupPosition +
|
||||
// ", childPosition:" + childPosition + " convertView:" + convertView);
|
||||
|
||||
final ViewHolder holder;
|
||||
ListItem childItem = getChild(groupPosition, childPosition);
|
||||
if (null == childItem) {
|
||||
throw new RuntimeException("list item is never null. pos:" + groupPosition);
|
||||
}
|
||||
|
||||
if (null == convertView) {
|
||||
holder = createViewHolder(parent, childItem);
|
||||
convertView = holder.itemView;
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
childItem.updateHolder(holder, groupPosition, childPosition);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRealChildrenCount(int groupPosition) {
|
||||
GroupItem groupItem = getGroup(groupPosition);
|
||||
if (null != groupItem.getChildItems()) {
|
||||
return groupItem.getChildItems().size();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getGroupId(int groupPosition) {
|
||||
return groupPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
|
||||
// Log.d("BaseAnimatedExpandableListAdapter:", "getGroupView:" + groupPosition + " convertView:" + convertView);
|
||||
|
||||
final ViewHolder holder;
|
||||
final ListItem item = getGroup(groupPosition);
|
||||
|
||||
if (null == item) {
|
||||
throw new RuntimeException("list item is never null. pos:" + groupPosition);
|
||||
}
|
||||
|
||||
if (null == convertView) {
|
||||
holder = createViewHolder(parent, item);
|
||||
convertView = holder.itemView;
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
item.updateHolder(holder, groupPosition, -1);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildSelectable(int arg0, int arg1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private ViewHolder createViewHolder(ViewGroup group, ListItem item) {
|
||||
return item.createViewHolder(group);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/7.
|
||||
*/
|
||||
public abstract class BaseGroupItem extends BaseListItem implements GroupItem {
|
||||
|
||||
protected OnIndicatorClickListener mIndicatorListener;
|
||||
protected int mGroupPos, mChildPos;
|
||||
protected List<ListItem> mChildItems = new ArrayList<ListItem>(1);
|
||||
|
||||
public BaseGroupItem(Context mContext) {
|
||||
super(mContext);
|
||||
}
|
||||
|
||||
public BaseGroupItem(Context mContext, int viewType) {
|
||||
super(mContext, viewType);
|
||||
}
|
||||
|
||||
public void setMoreItem(ListItem item) {
|
||||
mChildItems.add(0, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ListItem> getChildItems() {
|
||||
return mChildItems;
|
||||
}
|
||||
|
||||
public static <E, T> List<E> createLineItems(Context context, Class<E> lineItemClazz, List<T> list, Class<T> voClazz, Integer viewType, Integer column) {
|
||||
List<E> items = new ArrayList<E>();
|
||||
try {
|
||||
if (list != null) {
|
||||
int size = list.size();
|
||||
int rows = size % column == 0 ? size / column : size / column + 1;
|
||||
for (int i = 0; i < rows; i++) {
|
||||
T[] albums;
|
||||
if (i != rows - 1) {
|
||||
albums = (T[]) Array.newInstance(voClazz, column);
|
||||
for (int index = 0; index < column; ++index) {
|
||||
albums[index] = list.get(i * column + index);
|
||||
}
|
||||
} else {
|
||||
albums = (T[]) Array.newInstance(voClazz, column);
|
||||
int j = 0;
|
||||
while (size > i * column + j) {
|
||||
albums[j] = list.get(i * column + j++);
|
||||
}
|
||||
}
|
||||
|
||||
Constructor<E> constructor = lineItemClazz.getDeclaredConstructor(Context.class, Integer.class, albums.getClass());
|
||||
E lineItem = constructor.newInstance(context, viewType, albums);
|
||||
items.add(lineItem);
|
||||
}
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e("BaseGroupItem:", "createLineItems NoSuchMethodException", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e("BaseGroupItem:", "createLineItems InvocationTargetException", e);
|
||||
} catch (InstantiationException e) {
|
||||
Log.e("BaseGroupItem:", "createLineItems InstantiationException", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e("BaseGroupItem:", "createLineItems IllegalAccessException", e);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static interface OnIndicatorClickListener {
|
||||
void onClick(View v, int groupPos, int childPos);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/12.
|
||||
*/
|
||||
public abstract class BaseListAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract ListItem getItem(int position);
|
||||
|
||||
@Override
|
||||
public long getItemId(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return getItem(position).getViewType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final ViewHolder holder;
|
||||
final ListItem item = getItem(position);
|
||||
|
||||
if (null == item) {
|
||||
throw new RuntimeException("list item is never null. pos:" + position);
|
||||
}
|
||||
|
||||
if (null == convertView) {
|
||||
holder = createViewHolder(parent, item);
|
||||
convertView = holder.itemView;
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
item.updateHolder(holder, position, -1);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
private ViewHolder createViewHolder(ViewGroup group, ListItem item) {
|
||||
return item.createViewHolder(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
ListItem item = getItem(position);
|
||||
if (null != item) {
|
||||
return item.isEnabled();
|
||||
}
|
||||
|
||||
return super.isEnabled(position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Creator: 舒强睿
|
||||
* Date:2015/4/24
|
||||
* Time:19:03
|
||||
* <p/>
|
||||
* Description:
|
||||
*/
|
||||
public class BaseListItem implements ListItem {
|
||||
|
||||
protected Context mContext;
|
||||
protected int viewType;
|
||||
protected boolean isSelected;
|
||||
|
||||
public BaseListItem(Context mContext) {
|
||||
this.mContext = mContext;
|
||||
}
|
||||
|
||||
public BaseListItem(Context mContext, int viewType) {
|
||||
this.mContext = mContext;
|
||||
this.viewType = viewType;
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ViewHolder createViewHolder(ViewGroup group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHolder(ViewHolder holder, int groupPos, int childPos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType() {
|
||||
return viewType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
}
|
11
library/src/main/java/com/chwl/library/list/GroupItem.java
Normal file
11
library/src/main/java/com/chwl/library/list/GroupItem.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/7.
|
||||
*/
|
||||
public interface GroupItem extends ListItem {
|
||||
|
||||
public List<ListItem> getChildItems();
|
||||
}
|
20
library/src/main/java/com/chwl/library/list/ListItem.java
Normal file
20
library/src/main/java/com/chwl/library/list/ListItem.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/7.
|
||||
*/
|
||||
public interface ListItem {
|
||||
|
||||
ViewHolder createViewHolder(ViewGroup group);
|
||||
|
||||
void updateHolder(ViewHolder holder, int groupPos, int childPos);
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean isSelected();
|
||||
|
||||
int getViewType();
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
public class NoScrollingLinearLayoutManager extends LinearLayoutManager {
|
||||
|
||||
private boolean isScrollEnabled = true;
|
||||
|
||||
public NoScrollingLinearLayoutManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NoScrollingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
|
||||
public NoScrollingLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public void setScrollEnabled(boolean flag) {
|
||||
this.isScrollEnabled = flag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScrollVertically() {
|
||||
//Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
|
||||
return isScrollEnabled && super.canScrollVertically();
|
||||
}
|
||||
}
|
18
library/src/main/java/com/chwl/library/list/ViewHolder.java
Normal file
18
library/src/main/java/com/chwl/library/list/ViewHolder.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.chwl.library.list;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by lijun on 2014/11/7.
|
||||
*/
|
||||
public abstract class ViewHolder {
|
||||
|
||||
public final View itemView;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
if (itemView == null) {
|
||||
throw new IllegalArgumentException("itemView may not be null");
|
||||
}
|
||||
this.itemView = itemView;
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package com.chwl.library.manager;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Debug;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* <p> </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2018/1/26
|
||||
*/
|
||||
public class TracingManager {
|
||||
|
||||
|
||||
private boolean mDebug;
|
||||
|
||||
private static class TracingManagerHelper {
|
||||
private static final TracingManager INSTANCE = new TracingManager();
|
||||
}
|
||||
|
||||
|
||||
public static TracingManager get() {
|
||||
return TracingManagerHelper.INSTANCE;
|
||||
}
|
||||
|
||||
public void init(Application application, boolean debug) {
|
||||
mDebug = debug;
|
||||
}
|
||||
|
||||
public void startMethodTracing(@NonNull String tracePath) {
|
||||
if (!mDebug) return;
|
||||
Debug.startMethodTracing(tracePath);
|
||||
}
|
||||
|
||||
public void stopMethodTracing() {
|
||||
if (!mDebug) return;
|
||||
Debug.stopMethodTracing();
|
||||
}
|
||||
|
||||
}
|
69
library/src/main/java/com/chwl/library/net/rxnet/RxNet.java
Normal file
69
library/src/main/java/com/chwl/library/net/rxnet/RxNet.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package com.chwl.library.net.rxnet;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.net.rxnet.manager.RxNetManager;
|
||||
import com.chwl.library.utils.NullUtils;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
|
||||
/**
|
||||
* <p> 网络请求入口</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
|
||||
public final class RxNet {
|
||||
|
||||
private static RxNet mInstance;
|
||||
|
||||
private static RxNetManager.Builder sBuilder;
|
||||
|
||||
private static Context mContext;
|
||||
|
||||
private RxNet() {
|
||||
sBuilder = new RxNetManager.Builder();
|
||||
sBuilder.setContext(mContext);
|
||||
}
|
||||
|
||||
public static RxNet get() {
|
||||
if (mInstance == null) {
|
||||
synchronized (RxNet.class) {
|
||||
if (mInstance == null) {
|
||||
mInstance = new RxNet();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public static RxNetManager.Builder init(Context context) {
|
||||
mContext = context;
|
||||
get();
|
||||
return sBuilder;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static <T> T create(Class<T> service) {
|
||||
checkInstance();
|
||||
return sBuilder.getRxNetManager().getRetrofit().create(service);
|
||||
}
|
||||
/**
|
||||
* 刷新 BaseUrl
|
||||
*/
|
||||
public static void refreshBaseUrl(String newBaseUrl) {
|
||||
checkInstance();
|
||||
sBuilder.getRxNetManager().refreshBaseUrl(newBaseUrl);
|
||||
}
|
||||
|
||||
|
||||
private static void checkInstance() {
|
||||
NullUtils.checkNull(mInstance, ResUtil.getString(R.string.net_rxnet_rxnet_01));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.chwl.library.net.rxnet.callback;
|
||||
|
||||
/**
|
||||
* <p> 网络请求回调</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/6
|
||||
*/
|
||||
public interface CallBack<T> {
|
||||
/**
|
||||
* 网络请求成功
|
||||
*
|
||||
* @param data 返回数据
|
||||
*/
|
||||
void onSuccess(T data);
|
||||
|
||||
/**
|
||||
* 获取数据失败回调方法
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param error 失败的信息
|
||||
*/
|
||||
void onFail(int code, String error);
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* 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.chwl.library.net.rxnet.converter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Converter;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
/**
|
||||
* A {@linkplain Converter.Factory converter} which uses Gson for JSON.
|
||||
*
|
||||
* <p>Because Gson is so flexible in the types it supports, this converter assumes that it can
|
||||
* handle all types. If you are mixing JSON serialization with something else (such as protocol
|
||||
* buffers), you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this
|
||||
* instance} last to allow the other converters a chance to see their types.
|
||||
*/
|
||||
public final class GsonConverterFactory extends Converter.Factory {
|
||||
/**
|
||||
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
|
||||
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
|
||||
*/
|
||||
public static GsonConverterFactory create() {
|
||||
return create(new Gson());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance using {@code gson} for conversion. Encoding to JSON and decoding from JSON
|
||||
* (when no charset is specified by a header) will use UTF-8.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
|
||||
public static GsonConverterFactory create(Gson gson) {
|
||||
if (gson == null) throw new NullPointerException("gson == null");
|
||||
return new GsonConverterFactory(gson);
|
||||
}
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private GsonConverterFactory(Gson gson) {
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Converter<ResponseBody, ?> responseBodyConverter(
|
||||
Type type, Annotation[] annotations, Retrofit retrofit) {
|
||||
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
|
||||
return new GsonResponseBodyConverter<>(gson, adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Converter<?, RequestBody> requestBodyConverter(
|
||||
Type type,
|
||||
Annotation[] parameterAnnotations,
|
||||
Annotation[] methodAnnotations,
|
||||
Retrofit retrofit) {
|
||||
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
|
||||
return new GsonRequestBodyConverter<>(gson, adapter);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.chwl.library.net.rxnet.converter;
|
||||
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
public class GsonConverterPlugins {
|
||||
|
||||
private static Function1<Object, Object> resultHandler;
|
||||
static volatile boolean lockdown;
|
||||
|
||||
public static void onConvertResult(@Nullable Object object) {
|
||||
if (resultHandler != null && object != null) {
|
||||
resultHandler.invoke(object);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setResultHandler(Function1<Object, Object> handler) {
|
||||
if (lockdown) {
|
||||
throw new IllegalStateException("GsonConverterPlugins can't be changed anymore");
|
||||
}
|
||||
resultHandler = handler;
|
||||
}
|
||||
|
||||
public static void lockdown() {
|
||||
lockdown = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* 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.chwl.library.net.rxnet.converter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.Buffer;
|
||||
import retrofit2.Converter;
|
||||
|
||||
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
|
||||
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private final Gson gson;
|
||||
private final TypeAdapter<T> adapter;
|
||||
|
||||
GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
|
||||
this.gson = gson;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBody convert(@NotNull T value) throws IOException {
|
||||
Buffer buffer = new Buffer();
|
||||
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
|
||||
JsonWriter jsonWriter = gson.newJsonWriter(writer);
|
||||
adapter.write(jsonWriter, value);
|
||||
jsonWriter.close();
|
||||
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* 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.chwl.library.net.rxnet.converter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Converter;
|
||||
|
||||
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
|
||||
private final Gson gson;
|
||||
private final TypeAdapter<T> adapter;
|
||||
|
||||
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
|
||||
this.gson = gson;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(ResponseBody value) throws IOException {
|
||||
JsonReader jsonReader = gson.newJsonReader(value.charStream());
|
||||
try {
|
||||
T result = adapter.read(jsonReader);
|
||||
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
|
||||
throw new JsonIOException("JSON document was not fully consumed.");
|
||||
}
|
||||
//hook
|
||||
GsonConverterPlugins.onConvertResult(result);
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
value.close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package com.chwl.library.net.rxnet.exception
|
||||
|
||||
/**
|
||||
* Created by qisan 2022/5/26
|
||||
* com.qisan.wanandroid.http.exception
|
||||
*/
|
||||
class ApiException : RuntimeException {
|
||||
|
||||
private var code: Int? = null
|
||||
|
||||
constructor(throwable: Throwable, code: Int) : super(throwable) {
|
||||
this.code = code
|
||||
}
|
||||
|
||||
constructor(message: String) : super(Throwable(message))
|
||||
|
||||
constructor(message: String,code: Int) : super(Throwable(message)){
|
||||
this.code = code
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package com.chwl.library.net.rxnet.exception
|
||||
|
||||
/**
|
||||
* Created by qisan 2022/5/26
|
||||
* com.qisan.wanandroid.http.exception
|
||||
*/
|
||||
object ErrorStatus {
|
||||
/**
|
||||
* 响应成功
|
||||
*/
|
||||
const val SUCCESS = 0
|
||||
|
||||
/**
|
||||
* Token 过期
|
||||
*/
|
||||
const val TOKEN_INVALID = 401
|
||||
|
||||
/**
|
||||
* 未知错误
|
||||
*/
|
||||
const val UNKNOWN_ERROR = 1002
|
||||
|
||||
/**
|
||||
* 服务器内部错误
|
||||
*/
|
||||
const val SERVER_ERROR = 1003
|
||||
|
||||
/**
|
||||
* 网络连接超时
|
||||
*/
|
||||
const val NETWORK_ERROR = 1004
|
||||
|
||||
/**
|
||||
* API解析异常(或者第三方数据结构更改)等其他异常
|
||||
*/
|
||||
const val API_ERROR = 1005
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package com.chwl.library.net.rxnet.exception
|
||||
|
||||
import android.util.Log
|
||||
import com.chwl.library.R
|
||||
import com.chwl.library.utils.ResUtil
|
||||
import com.google.gson.JsonParseException
|
||||
import org.json.JSONException
|
||||
import retrofit2.HttpException
|
||||
import java.net.ConnectException
|
||||
import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import java.text.ParseException
|
||||
|
||||
/**
|
||||
* Created by wushaocheng 2023/1/31
|
||||
*/
|
||||
class ExceptionHandle {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExceptionHandle"
|
||||
var errorMsg = ResUtil.getString(R.string.request_failed_again_later)
|
||||
|
||||
fun handleException(e: Throwable): String {
|
||||
e.printStackTrace()
|
||||
when (e) {
|
||||
is SocketException, is SocketTimeoutException, is HttpException -> { //均视为网络错误
|
||||
Log.e(TAG, "網絡連接異常: " + e.message)
|
||||
errorMsg = ResUtil.getString(R.string.network_abnormality_check_again)
|
||||
}
|
||||
is JsonParseException, is JSONException, is ParseException -> { //均视为解析错误
|
||||
Log.e(TAG, "數據解析異常: " + e.message)
|
||||
errorMsg = ResUtil.getString(R.string.data_parsing_exception)
|
||||
}
|
||||
is ApiException -> {//服务器返回的错误信息
|
||||
errorMsg = e.message.toString()
|
||||
}
|
||||
is UnknownHostException -> {
|
||||
Log.e(TAG, "網絡連接異常: " + e.message)
|
||||
errorMsg = ResUtil.getString(R.string.network_abnormality_check_again)
|
||||
}
|
||||
is IllegalArgumentException -> {
|
||||
errorMsg = ResUtil.getString(R.string.parameter_error)
|
||||
}
|
||||
else -> {//未知错误
|
||||
try {
|
||||
Log.e(TAG, "錯誤: " + e.message)
|
||||
} catch (e1: Exception) {
|
||||
Log.e(TAG, "未知錯誤Debug調試 ")
|
||||
}
|
||||
errorMsg = e.message.toString()
|
||||
}
|
||||
}
|
||||
return errorMsg
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package com.chwl.library.net.rxnet.factory;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* Enables TLS v1.2 when creating SSLSockets.
|
||||
* <p/>
|
||||
* For some reason, android supports TLS v1.2 from API 16, but enables it by
|
||||
* default only from API 20.
|
||||
* @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
|
||||
* @see SSLSocketFactory
|
||||
*/
|
||||
public class Tls12SocketFactory extends SSLSocketFactory {
|
||||
private static final String[] TLS_V12_ONLY = {"TLSv1.2"};
|
||||
|
||||
final SSLSocketFactory delegate;
|
||||
|
||||
public Tls12SocketFactory(SSLSocketFactory base) {
|
||||
this.delegate = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return delegate.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return delegate.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return patch(delegate.createSocket(s, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return patch(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
||||
return patch(delegate.createSocket(host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return patch(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return patch(delegate.createSocket(address, port, localAddress, localPort));
|
||||
}
|
||||
|
||||
private Socket patch(Socket s) {
|
||||
if (s instanceof SSLSocket) {
|
||||
((SSLSocket) s).setEnabledProtocols(TLS_V12_ONLY);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
@@ -0,0 +1,179 @@
|
||||
package com.chwl.library.net.rxnet.https;
|
||||
|
||||
|
||||
import com.chwl.library.net.rxnet.utils.RxNetLog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/**
|
||||
* <p> Https工具类 (签名,自签名信息设置)</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
public class HttpsUtils {
|
||||
|
||||
public static class SSLParams {
|
||||
public SSLSocketFactory sSLSocketFactory;
|
||||
public X509TrustManager trustManager;
|
||||
}
|
||||
|
||||
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream[] certificates) {
|
||||
SSLParams sslParams = new SSLParams();
|
||||
try {
|
||||
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
|
||||
TrustManager[] trustManagers = prepareTrustManager(certificates);
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
X509TrustManager trustManager;
|
||||
if (trustManagers != null) {
|
||||
trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
|
||||
} else {
|
||||
trustManager = new UnSafeTrustManager();
|
||||
}
|
||||
sslContext.init(keyManagers, new TrustManager[]{trustManager}, null);
|
||||
sslParams.sSLSocketFactory = sslContext.getSocketFactory();
|
||||
sslParams.trustManager = trustManager;
|
||||
return sslParams;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (KeyManagementException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
|
||||
if (certificates == null || certificates.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null);
|
||||
int index = 0;
|
||||
for (InputStream certificate : certificates) {
|
||||
String certificateAlias = Integer.toString(index++);
|
||||
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
|
||||
try {
|
||||
if (certificate != null) {
|
||||
certificate.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
}
|
||||
}
|
||||
TrustManagerFactory trustManagerFactory;
|
||||
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(keyStore);
|
||||
return trustManagerFactory.getTrustManagers();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (CertificateException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (KeyStoreException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
|
||||
try {
|
||||
if (bksFile == null || password == null) {
|
||||
return null;
|
||||
}
|
||||
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
|
||||
clientKeyStore.load(bksFile, password.toCharArray());
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
keyManagerFactory.init(clientKeyStore, password.toCharArray());
|
||||
return keyManagerFactory.getKeyManagers();
|
||||
} catch (KeyStoreException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (CertificateException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
|
||||
for (TrustManager trustManager : trustManagers) {
|
||||
if (trustManager instanceof X509TrustManager) {
|
||||
return (X509TrustManager) trustManager;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class UnSafeTrustManager implements X509TrustManager {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyTrustManager implements X509TrustManager {
|
||||
private X509TrustManager defaultTrustManager;
|
||||
private X509TrustManager localTrustManager;
|
||||
|
||||
public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
|
||||
TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
var4.init((KeyStore) null);
|
||||
defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
|
||||
this.localTrustManager = localTrustManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
try {
|
||||
defaultTrustManager.checkServerTrusted(chain, authType);
|
||||
} catch (CertificateException ce) {
|
||||
localTrustManager.checkServerTrusted(chain, authType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
package com.chwl.library.net.rxnet.interceptor;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.net.rxnet.utils.RxNetLog;
|
||||
import com.chwl.library.net.rxnet.utils.RxNetWorkUtils;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* <p> http 缓存拦截器 </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
public class HttpCacheInterceptor implements Interceptor {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public HttpCacheInterceptor(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
if (!RxNetWorkUtils.isAvailable(mContext)) {
|
||||
request = request.newBuilder()
|
||||
.cacheControl(CacheControl.FORCE_CACHE)
|
||||
.build();
|
||||
RxNetLog.d(ResUtil.getString(R.string.rxnet_interceptor_httpcacheinterceptor_01));
|
||||
}
|
||||
Response originalResponse = chain.proceed(request);
|
||||
if (RxNetWorkUtils.isAvailable(mContext)) {
|
||||
//有网络的时候读取接口里面的配置,在这里进行统一配置
|
||||
String cacheControl = request.cacheControl().toString();
|
||||
return originalResponse.newBuilder()
|
||||
.header("Cache-Control", cacheControl)
|
||||
.removeHeader("Pragma")
|
||||
.build();
|
||||
} else {
|
||||
return originalResponse.newBuilder()
|
||||
.header("Cache-Control", "public, only-if-cached, max-stale=2419200")
|
||||
.removeHeader("Pragma")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* 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.chwl.library.net.rxnet.interceptor;
|
||||
|
||||
import static okhttp3.internal.platform.Platform.INFO;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.Connection;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okhttp3.internal.http.HttpHeaders;
|
||||
import okhttp3.internal.platform.Platform;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.GzipSource;
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor which logs request and response information. Can be applied as an
|
||||
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
|
||||
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
|
||||
* this class should not be considered stable and may change slightly between releases. If you need
|
||||
* a stable logging format, use your own interceptor.
|
||||
*/
|
||||
public final class HttpLoggingInterceptor implements Interceptor {
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
public enum Level {
|
||||
/** No logs. */
|
||||
NONE,
|
||||
/**
|
||||
* Logs request and response lines.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1 (3-byte body)
|
||||
*
|
||||
* <-- 200 OK (22ms, 6-byte body)
|
||||
* }</pre>
|
||||
*/
|
||||
BASIC,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
HEADERS,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers and bodies (if present).
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
*
|
||||
* Hi?
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
*
|
||||
* Hello!
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
BODY
|
||||
}
|
||||
|
||||
public interface Logger {
|
||||
void log(String message);
|
||||
|
||||
/** A {@link Logger} defaults output appropriate for the current platform. */
|
||||
Logger DEFAULT = message -> Platform.get().log( message, INFO,null);
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor() {
|
||||
this(Logger.DEFAULT);
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private volatile Set<String> headersToRedact = Collections.emptySet();
|
||||
|
||||
public void redactHeader(String name) {
|
||||
Set<String> newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
newHeadersToRedact.addAll(headersToRedact);
|
||||
newHeadersToRedact.add(name);
|
||||
headersToRedact = newHeadersToRedact;
|
||||
}
|
||||
|
||||
private volatile Level level = Level.NONE;
|
||||
|
||||
/** Change the level at which this interceptor logs. */
|
||||
public HttpLoggingInterceptor setLevel(Level level) {
|
||||
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
|
||||
this.level = level;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override public synchronized Response intercept(Chain chain) throws IOException {
|
||||
Level level = this.level;
|
||||
|
||||
Request request = chain.request();
|
||||
if (level == Level.NONE) {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
|
||||
boolean logBody = level == Level.BODY;
|
||||
boolean logHeaders = logBody || level == Level.HEADERS;
|
||||
|
||||
RequestBody requestBody = request.body();
|
||||
boolean hasRequestBody = requestBody != null;
|
||||
|
||||
Connection connection = chain.connection();
|
||||
String requestStartMessage = "--> "
|
||||
+ request.method()
|
||||
+ ' ' + request.url()
|
||||
+ (connection != null ? " " + connection.protocol() : "");
|
||||
if (!logHeaders && hasRequestBody) {
|
||||
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
|
||||
}
|
||||
logger.log(requestStartMessage);
|
||||
|
||||
if (logHeaders) {
|
||||
if (hasRequestBody) {
|
||||
// Request body headers are only present when installed as a network interceptor. Force
|
||||
// them to be included (when available) so there values are known.
|
||||
if (requestBody.contentType() != null) {
|
||||
logger.log("Content-Type: " + requestBody.contentType());
|
||||
}
|
||||
if (requestBody.contentLength() != -1) {
|
||||
logger.log("Content-Length: " + requestBody.contentLength());
|
||||
}
|
||||
}
|
||||
|
||||
Headers headers = request.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
String name = headers.name(i);
|
||||
// Skip headers from the request body as they are explicitly logged above.
|
||||
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
|
||||
logHeader(headers, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!logBody || !hasRequestBody) {
|
||||
logger.log("--> END " + request.method());
|
||||
} else if (bodyHasUnknownEncoding(request.headers())) {
|
||||
logger.log("--> END " + request.method() + " (encoded body omitted)");
|
||||
} else if (requestBody.isDuplex()) {
|
||||
logger.log("--> END " + request.method() + " (duplex request body omitted)");
|
||||
} else {
|
||||
Buffer buffer = new Buffer();
|
||||
requestBody.writeTo(buffer);
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = requestBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
logger.log("");
|
||||
if (isPlaintext(buffer)) {
|
||||
logger.log(buffer.readString(charset));
|
||||
logger.log("--> END " + request.method()
|
||||
+ " (" + requestBody.contentLength() + "-byte body)");
|
||||
} else {
|
||||
logger.log("--> END " + request.method() + " (binary "
|
||||
+ requestBody.contentLength() + "-byte body omitted)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long startNs = System.nanoTime();
|
||||
Response response;
|
||||
try {
|
||||
response = chain.proceed(request);
|
||||
} catch (Exception e) {
|
||||
logger.log("<-- HTTP FAILED: " + e);
|
||||
throw e;
|
||||
}
|
||||
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
long contentLength = responseBody.contentLength();
|
||||
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
|
||||
logger.log("<-- "
|
||||
+ response.code()
|
||||
+ (response.message().isEmpty() ? "" : ' ' + response.message())
|
||||
+ ' ' + response.request().url()
|
||||
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
|
||||
|
||||
if (logHeaders) {
|
||||
Headers headers = response.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
logHeader(headers, i);
|
||||
}
|
||||
|
||||
if (!logBody || !HttpHeaders.hasBody(response)) {
|
||||
logger.log("<-- END HTTP");
|
||||
} else if (bodyHasUnknownEncoding(response.headers())) {
|
||||
logger.log("<-- END HTTP (encoded body omitted)");
|
||||
} else {
|
||||
BufferedSource source = responseBody.source();
|
||||
source.request(Long.MAX_VALUE); // Buffer the entire body.
|
||||
Buffer buffer = source.getBuffer();
|
||||
|
||||
Long gzippedLength = null;
|
||||
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
|
||||
gzippedLength = buffer.size();
|
||||
try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) {
|
||||
buffer = new Buffer();
|
||||
buffer.writeAll(gzippedResponseBody);
|
||||
}
|
||||
}
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = responseBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
if (!isPlaintext(buffer)) {
|
||||
logger.log("");
|
||||
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (contentLength != 0) {
|
||||
logger.log("");
|
||||
logger.log(buffer.clone().readString(charset));
|
||||
}
|
||||
|
||||
if (gzippedLength != null) {
|
||||
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
|
||||
+ gzippedLength + "-gzipped-byte body)");
|
||||
} else {
|
||||
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void logHeader(Headers headers, int i) {
|
||||
String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i);
|
||||
logger.log(headers.name(i) + ": " + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the body in question probably contains human readable text. Uses a small sample
|
||||
* of code points to detect unicode control characters commonly used in binary file signatures.
|
||||
*/
|
||||
static boolean isPlaintext(Buffer buffer) {
|
||||
try {
|
||||
Buffer prefix = new Buffer();
|
||||
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
|
||||
buffer.copyTo(prefix, 0, byteCount);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (prefix.exhausted()) {
|
||||
break;
|
||||
}
|
||||
int codePoint = prefix.readUtf8CodePoint();
|
||||
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
return false; // Truncated UTF-8 sequence.
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean bodyHasUnknownEncoding(Headers headers) {
|
||||
String contentEncoding = headers.get("Content-Encoding");
|
||||
return contentEncoding != null
|
||||
&& !contentEncoding.equalsIgnoreCase("identity")
|
||||
&& !contentEncoding.equalsIgnoreCase("gzip");
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.chwl.library.net.rxnet.manager;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chwl.library.net.rxnet.interceptor.HttpCacheInterceptor;
|
||||
import com.chwl.library.net.rxnet.utils.RxFileUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import okhttp3.Cache;
|
||||
|
||||
/**
|
||||
* <p> 网络请求缓存管理</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
|
||||
final class CacheManager {
|
||||
/** 默认100M */
|
||||
private static final int MAX_CACHE = 100 * 1024 * 1024;
|
||||
|
||||
private Cache mCache;
|
||||
private HttpCacheInterceptor mHttpCacheInterceptor;
|
||||
|
||||
CacheManager(Context context) {
|
||||
File cacheFile = new File(RxFileUtils.getCacheDir(context), "net");
|
||||
mCache = new Cache(cacheFile, MAX_CACHE);
|
||||
mHttpCacheInterceptor = new HttpCacheInterceptor(context);
|
||||
}
|
||||
|
||||
public Cache getCache() {
|
||||
return mCache;
|
||||
}
|
||||
|
||||
public HttpCacheInterceptor getHttpCacheInterceptor() {
|
||||
return mHttpCacheInterceptor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,222 @@
|
||||
package com.chwl.library.net.rxnet.manager;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.chwl.library.BuildConfig;
|
||||
import com.chwl.library.net.rxnet.converter.GsonConverterFactory;
|
||||
import com.chwl.library.net.rxnet.https.HttpsUtils;
|
||||
import com.chwl.library.net.rxnet.utils.RxNetLog;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||
|
||||
/**
|
||||
* <p> RxNet 管理类 </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
|
||||
public final class RxNetManager {
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private OkHttpClient.Builder mBuilder;
|
||||
|
||||
private Retrofit mRetrofit;
|
||||
|
||||
RxNetManager(Context context, String baseUrl, Cache cache, int readTimeout,
|
||||
int writeTimeout, int connectTimeout, List<Interceptor> interceptors,
|
||||
HttpsUtils.SSLParams sslParams, HostnameVerifier hostnameVerifier) {
|
||||
|
||||
mBuilder = new OkHttpClient.Builder();
|
||||
|
||||
if (RxNetLog.DEBUG) {
|
||||
//正式环境千万不要加这玩意,为了方便日志查看,拦截器里面加了synchronized关键字,接口请求是串行的
|
||||
// HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||
// @Override
|
||||
// public void log(String message) {
|
||||
// RxNetLog.d("OKHttp-------%s", message);
|
||||
// }
|
||||
// });
|
||||
okhttp3.logging.HttpLoggingInterceptor loggingInterceptor = new okhttp3.logging.HttpLoggingInterceptor();
|
||||
loggingInterceptor.setLevel(okhttp3.logging.HttpLoggingInterceptor.Level.BODY);
|
||||
mBuilder.addInterceptor(loggingInterceptor);
|
||||
}
|
||||
|
||||
for (Interceptor interceptor : interceptors) {
|
||||
mBuilder.addInterceptor(interceptor);
|
||||
}
|
||||
|
||||
//mBuilder.readTimeout(readTimeout > 0 ? readTimeout : DEFAULT_READ_TIME_OUT, TimeUnit.MILLISECONDS)
|
||||
// .writeTimeout(writeTimeout > 0 ? writeTimeout : DEFAULT_WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
|
||||
// .connectTimeout(connectTimeout > 0 ? connectTimeout : DEFAULT_CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
|
||||
// .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
|
||||
// 无代理设置,防止被抓包
|
||||
// 指定只要不是 release 包都可以抓包,方便测试进行验证问题
|
||||
if (Objects.equals(BuildConfig.BUILD_TYPE, "release")) {
|
||||
mBuilder.proxySelector(new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
return Collections.singletonList(Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress socketAddress, IOException e) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mOkHttpClient = mBuilder.build();
|
||||
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
|
||||
mRetrofit = new Retrofit.Builder()
|
||||
.client(mOkHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.baseUrl(baseUrl)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
public void refreshBaseUrl(String baseUrl) {
|
||||
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
|
||||
mRetrofit = new Retrofit.Builder()
|
||||
.client(mOkHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.baseUrl(baseUrl)
|
||||
.build();
|
||||
}
|
||||
|
||||
public Retrofit getRetrofit() {
|
||||
return mRetrofit;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private String baseUrl;
|
||||
private Context mContext;
|
||||
private Cache mCache;
|
||||
|
||||
private int readTimeout;
|
||||
private int writeTimeout;
|
||||
private int connectTimeout;
|
||||
|
||||
private List<Interceptor> interceptors;
|
||||
private HttpsUtils.SSLParams sslParams;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
|
||||
private RxNetManager mRxNetManager;
|
||||
|
||||
public Builder debug(boolean isDebug) {
|
||||
RxNetLog.DEBUG = isDebug;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setContext(Context context) {
|
||||
mContext = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReadTimeout(int readTimeout) {
|
||||
this.readTimeout = readTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setWriteTimeout(int writeTimeout) {
|
||||
this.writeTimeout = writeTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCache(Cache cache) {
|
||||
mCache = cache;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加拦截器
|
||||
*
|
||||
* @param interceptor
|
||||
*/
|
||||
public Builder addInterceptors(Interceptor interceptor) {
|
||||
if (interceptors == null) {
|
||||
interceptors = new ArrayList<>();
|
||||
}
|
||||
this.interceptors.add(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* https的全局自签名证书
|
||||
*
|
||||
* @param certificates
|
||||
* @return
|
||||
*/
|
||||
public Builder certificates(InputStream... certificates) {
|
||||
this.sslParams = HttpsUtils.getSslSocketFactory(null, null, certificates);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* https双向认证证书
|
||||
*
|
||||
* @param bksFile
|
||||
* @param password
|
||||
* @param certificates
|
||||
* @return
|
||||
*/
|
||||
public Builder certificates(InputStream bksFile, String password, InputStream... certificates) {
|
||||
this.sslParams = HttpsUtils.getSslSocketFactory(bksFile, password, certificates);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* https的全局访问规则
|
||||
*
|
||||
* @param hostnameVerifier
|
||||
* @return
|
||||
*/
|
||||
public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) {
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public void build() {
|
||||
mRxNetManager = new RxNetManager(mContext, baseUrl, mCache, readTimeout, writeTimeout,
|
||||
connectTimeout, interceptors, sslParams, hostnameVerifier);
|
||||
}
|
||||
|
||||
public RxNetManager getRxNetManager() {
|
||||
return mRxNetManager;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,271 @@
|
||||
package com.chwl.library.net.rxnet.model;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.chwl.library.net.rxnet.RxNet;
|
||||
import com.chwl.library.net.rxnet.utils.RxNetLog;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* <p> http头部 实体封装</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/7
|
||||
*/
|
||||
public class HttpHeaders {
|
||||
public static final String FORMAT_HTTP_DATA = "EEE, dd MMM y HH:mm:ss 'GMT'";
|
||||
public static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT");
|
||||
|
||||
public static final String HEAD_KEY_RESPONSE_CODE = "ResponseCode";
|
||||
public static final String HEAD_KEY_RESPONSE_MESSAGE = "ResponseMessage";
|
||||
public static final String HEAD_KEY_ACCEPT = "Accept";
|
||||
public static final String HEAD_KEY_ACCEPT_ENCODING = "Accept-Encoding";
|
||||
public static final String HEAD_VALUE_ACCEPT_ENCODING = "gzip, deflate";
|
||||
public static final String HEAD_KEY_ACCEPT_LANGUAGE = "Accept-Language";
|
||||
public static final String HEAD_KEY_CONTENT_TYPE = "Content-Type";
|
||||
public static final String HEAD_KEY_CONTENT_LENGTH = "Content-Length";
|
||||
public static final String HEAD_KEY_CONTENT_ENCODING = "Content-Encoding";
|
||||
public static final String HEAD_KEY_CONTENT_DISPOSITION = "Content-Disposition";
|
||||
public static final String HEAD_KEY_CONTENT_RANGE = "Content-Range";
|
||||
public static final String HEAD_KEY_CACHE_CONTROL = "Cache-Control";
|
||||
public static final String HEAD_KEY_CONNECTION = "Connection";
|
||||
public static final String HEAD_VALUE_CONNECTION_KEEP_ALIVE = "keep-alive";
|
||||
public static final String HEAD_VALUE_CONNECTION_CLOSE = "close";
|
||||
public static final String HEAD_KEY_DATE = "Date";
|
||||
public static final String HEAD_KEY_EXPIRES = "Expires";
|
||||
public static final String HEAD_KEY_E_TAG = "ETag";
|
||||
public static final String HEAD_KEY_PRAGMA = "Pragma";
|
||||
public static final String HEAD_KEY_IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||
public static final String HEAD_KEY_IF_NONE_MATCH = "If-None-Match";
|
||||
public static final String HEAD_KEY_LAST_MODIFIED = "Last-Modified";
|
||||
public static final String HEAD_KEY_LOCATION = "Location";
|
||||
public static final String HEAD_KEY_USER_AGENT = "User-Agent";
|
||||
public static final String HEAD_KEY_COOKIE = "Cookie";
|
||||
public static final String HEAD_KEY_COOKIE2 = "Cookie2";
|
||||
public static final String HEAD_KEY_SET_COOKIE = "Set-Cookie";
|
||||
public static final String HEAD_KEY_SET_COOKIE2 = "Set-Cookie2";
|
||||
|
||||
public LinkedHashMap<String, String> headersMap;
|
||||
private static String acceptLanguage;
|
||||
private static String userAgent;
|
||||
|
||||
private void init() {
|
||||
headersMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public HttpHeaders() {
|
||||
init();
|
||||
}
|
||||
|
||||
public HttpHeaders(String key, String value) {
|
||||
init();
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
public void put(String key, String value) {
|
||||
if (key != null && value != null) {
|
||||
headersMap.remove(key);
|
||||
headersMap.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void put(HttpHeaders headers) {
|
||||
if (headers != null) {
|
||||
if (headers.headersMap != null && !headers.headersMap.isEmpty()) {
|
||||
Set<Map.Entry<String, String>> set = headers.headersMap.entrySet();
|
||||
for (Map.Entry<String, String> map : set) {
|
||||
headersMap.remove(map.getKey());
|
||||
headersMap.put(map.getKey(), map.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return headersMap.isEmpty();
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return headersMap.get(key);
|
||||
}
|
||||
|
||||
public String remove(String key) {
|
||||
return headersMap.remove(key);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
headersMap.clear();
|
||||
}
|
||||
|
||||
public Set<String> getNames() {
|
||||
return headersMap.keySet();
|
||||
}
|
||||
|
||||
public final String toJSONString() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
try {
|
||||
for (Map.Entry<String, String> entry : headersMap.entrySet()) {
|
||||
jsonObject.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
RxNetLog.e(e.getMessage());
|
||||
}
|
||||
return jsonObject.toString();
|
||||
}
|
||||
|
||||
public static long getDate(String gmtTime) {
|
||||
try {
|
||||
return parseGMTToMillis(gmtTime);
|
||||
} catch (ParseException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDate(long milliseconds) {
|
||||
return formatMillisToGMT(milliseconds);
|
||||
}
|
||||
|
||||
public static long getExpiration(String expiresTime) {
|
||||
try {
|
||||
return parseGMTToMillis(expiresTime);
|
||||
} catch (ParseException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getLastModified(String lastModified) {
|
||||
try {
|
||||
return parseGMTToMillis(lastModified);
|
||||
} catch (ParseException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCacheControl(String cacheControl, String pragma) {
|
||||
// first http1.1, second http1.0
|
||||
if (cacheControl != null) {
|
||||
return cacheControl;
|
||||
} else if (pragma != null) {
|
||||
return pragma;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAcceptLanguage(String language) {
|
||||
acceptLanguage = language;
|
||||
}
|
||||
|
||||
public static String getAcceptLanguage() {
|
||||
if (TextUtils.isEmpty(acceptLanguage)) {
|
||||
Locale locale = Locale.getDefault();
|
||||
String language = locale.getLanguage();
|
||||
String country = locale.getCountry();
|
||||
StringBuilder acceptLanguageBuilder = new StringBuilder(language);
|
||||
if (!TextUtils.isEmpty(country)) {
|
||||
acceptLanguageBuilder.append('-').append(country).append(',').append(language).append(";q=0.8");
|
||||
}
|
||||
acceptLanguage = acceptLanguageBuilder.toString();
|
||||
return acceptLanguage;
|
||||
}
|
||||
return acceptLanguage;
|
||||
}
|
||||
|
||||
public static void setUserAgent(String agent) {
|
||||
userAgent = agent;
|
||||
}
|
||||
|
||||
|
||||
public static String getUserAgent() {
|
||||
if (TextUtils.isEmpty(userAgent)) {
|
||||
String webUserAgent = null;
|
||||
try {
|
||||
Class<?> sysResCls = Class.forName("com.android.internal.R$string");
|
||||
Field webUserAgentField = sysResCls.getDeclaredField("web_user_agent");
|
||||
Integer resId = (Integer) webUserAgentField.get(null);
|
||||
webUserAgent = RxNet.getContext().getString(resId);
|
||||
} catch (Exception e) {
|
||||
// We have nothing to do
|
||||
}
|
||||
if (TextUtils.isEmpty(webUserAgent)) {
|
||||
webUserAgent = "Mozilla/5.0 (Linux; U; Android %s) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 %sSafari/533.1";
|
||||
}
|
||||
|
||||
Locale locale = Locale.getDefault();
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
// Add version
|
||||
final String version = Build.VERSION.RELEASE;
|
||||
if (version.length() > 0) {
|
||||
buffer.append(version);
|
||||
} else {
|
||||
// default to "1.0"
|
||||
buffer.append("1.0");
|
||||
}
|
||||
buffer.append("; ");
|
||||
final String language = locale.getLanguage();
|
||||
if (language != null) {
|
||||
buffer.append(language.toLowerCase(locale));
|
||||
final String country = locale.getCountry();
|
||||
if (!TextUtils.isEmpty(country)) {
|
||||
buffer.append("-");
|
||||
buffer.append(country.toLowerCase(locale));
|
||||
}
|
||||
} else {
|
||||
// default to "en"
|
||||
buffer.append("en");
|
||||
}
|
||||
// add the model for the release build
|
||||
if ("REL".equals(Build.VERSION.CODENAME)) {
|
||||
final String model = Build.MODEL;
|
||||
if (model.length() > 0) {
|
||||
buffer.append("; ");
|
||||
buffer.append(model);
|
||||
}
|
||||
}
|
||||
final String id = Build.ID;
|
||||
if (id.length() > 0) {
|
||||
buffer.append(" Build/");
|
||||
buffer.append(id);
|
||||
}
|
||||
userAgent = String.format(webUserAgent, buffer, "Mobile ");
|
||||
return userAgent;
|
||||
}
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public static long parseGMTToMillis(String gmtTime) throws ParseException {
|
||||
if (TextUtils.isEmpty(gmtTime)) {
|
||||
return 0;
|
||||
}
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_HTTP_DATA, Locale.US);
|
||||
formatter.setTimeZone(GMT_TIME_ZONE);
|
||||
Date date = formatter.parse(gmtTime);
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
public static String formatMillisToGMT(long milliseconds) {
|
||||
Date date = new Date(milliseconds);
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT_HTTP_DATA, Locale.US);
|
||||
simpleDateFormat.setTimeZone(GMT_TIME_ZONE);
|
||||
return simpleDateFormat.format(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpHeaders{" + "headersMap=" + headersMap + '}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
package com.chwl.library.net.rxnet.utils
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.chwl.library.common.util.doLog
|
||||
import com.chwl.library.common.util.isVerify
|
||||
import com.chwl.library.utils.TimeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
|
||||
object NetworkLatencyChecker {
|
||||
|
||||
|
||||
/**
|
||||
* 检查域名列表并返回延迟最低的域名
|
||||
* @param domains 要检查的域名列表
|
||||
* @param scope 协程作用域(推荐使用 ViewModel 的 viewModelScope)
|
||||
* @param port 要测试的端口(默认 HTTPS 443 端口)
|
||||
* @param timeout 连接超时时间(毫秒)
|
||||
* @param resultCallback 结果回调(主线程执行)
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun findLowestLatencyDomain(
|
||||
domains: List<String>,
|
||||
scope: CoroutineScope,
|
||||
port: Int = 443,
|
||||
timeout: Int = 5000,
|
||||
resultCallback: (String?) -> Unit
|
||||
) {
|
||||
scope.launch {
|
||||
val deferredResults = domains.map { domain ->
|
||||
async(Dispatchers.IO) {
|
||||
try {
|
||||
val socket = Socket()
|
||||
val startTime = System.currentTimeMillis()
|
||||
socket.connect(InetSocketAddress(domain, port), timeout)
|
||||
val latency = System.currentTimeMillis() - startTime
|
||||
socket.close()
|
||||
domain to latency
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val validResults = deferredResults.awaitAll()
|
||||
.filterNotNull()
|
||||
.filter { it.second > 0 } // 过滤无效结果
|
||||
|
||||
val bestDomain = validResults.minByOrNull { it.second }?.first
|
||||
|
||||
// 切回主线程返回结果
|
||||
withContext(Dispatchers.Main) {
|
||||
resultCallback(bestDomain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun use(domains: List<String>,
|
||||
port: Int = 443,
|
||||
timeout: Int = 1000,
|
||||
resultCallback: (String?) -> Unit) {
|
||||
" 域名 ping 启动---->".doLog()
|
||||
|
||||
GlobalScope.launch {
|
||||
val deferredResults = domains.map { domain ->
|
||||
async(Dispatchers.IO) {
|
||||
try {
|
||||
val socket = Socket()
|
||||
val startTime = System.currentTimeMillis()
|
||||
socket.connect(InetSocketAddress(domain, port), timeout)
|
||||
val currentTimeMillis = System.currentTimeMillis()
|
||||
val latency = currentTimeMillis - startTime
|
||||
" 域名 ping 成功----> $domain startTime= ${TimeUtils.getTimeStringFromMillis(startTime)} currentTimeMillis = ${TimeUtils.getTimeStringFromMillis(currentTimeMillis)} latency=$latency".doLog()
|
||||
socket.close()
|
||||
domain to latency
|
||||
} catch (e: Exception) {
|
||||
" 域名 ping 失败报错----> $domain 报错 error = ${e.message}".doLog()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val validResults = deferredResults.awaitAll()
|
||||
.filterNotNull()
|
||||
.filter { it.second > 0 } // 过滤无效结果
|
||||
|
||||
val bestDomain = validResults.minByOrNull { it.second }?.first
|
||||
" 域名 ping 结果展示----> 耗时最短 $bestDomain".doLog()
|
||||
|
||||
var find = ""
|
||||
try {
|
||||
find = validResults.find { it.first.equals("api.molistars.com") }?.first?.toString()?:""
|
||||
if (find.isVerify()) {
|
||||
" 域名 ping 结果展示----> 有效域名包含 $find".doLog()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
// 切回主线程返回结果
|
||||
withContext(Dispatchers.Main) {
|
||||
if (find.isVerify()) {
|
||||
" 域名 ping 结果展示----> 返回 find域名".doLog()
|
||||
resultCallback(find)
|
||||
} else {
|
||||
if (bestDomain.isVerify()) {
|
||||
" 域名 ping 结果展示----> 返回 耗时最短bestDomain域名".doLog()
|
||||
resultCallback(bestDomain)
|
||||
} else {
|
||||
" 域名 ping 结果展示----> 异常返回null".doLog()
|
||||
resultCallback(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
" 域名 ping 结束---->".doLog()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 域名切换完毕, 通知监听者 执行后续逻辑
|
||||
*/
|
||||
fun checkFinishes(){
|
||||
mCheckStatusData.value = true
|
||||
}
|
||||
|
||||
var mCheckStatusData = MutableLiveData<Boolean>(false)
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package com.chwl.library.net.rxnet.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* <p> 网络文件工具类</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
|
||||
public class RxFileUtils {
|
||||
|
||||
/**
|
||||
* 获取缓存目录,有限获取/sdcard/Android/data/package_name/cache,失败才获取/data/data/com.android.framework/cache
|
||||
*
|
||||
* @param context
|
||||
* @return 返回缓存路径
|
||||
*/
|
||||
public static String getCacheDir(Context context) {
|
||||
// /data/data/com.android.framework/cache
|
||||
File cacheDir = context.getCacheDir();
|
||||
// /sdcard/Android/data/package_name/cache
|
||||
File externalCacheDir = context.getExternalCacheDir();
|
||||
String cacheDirStr;
|
||||
if (externalCacheDir == null) {
|
||||
cacheDirStr = cacheDir.getAbsolutePath();
|
||||
} else {
|
||||
cacheDirStr = checkSdCard() ? externalCacheDir.getAbsolutePath() : cacheDir.getAbsolutePath();
|
||||
}
|
||||
return cacheDirStr;
|
||||
}
|
||||
|
||||
public static boolean checkSdCard() {
|
||||
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否存在
|
||||
*
|
||||
* @param file 文件
|
||||
* @return {@code true}: 存在<br>{@code false}: 不存在
|
||||
*/
|
||||
public static boolean isFileExists(File file) {
|
||||
return file != null && file.exists();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package com.chwl.library.net.rxnet.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* <p> 网络日志</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
public class RxNetLog {
|
||||
public static final String TAG = "RxNet_LOG";
|
||||
public static boolean DEBUG = false;
|
||||
|
||||
public static void i(String format, Object... args) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String format, Object... args) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String format, Object... args) {
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(String format, Object... args) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, String.format(format, args));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package com.chwl.library.net.rxnet.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import com.chwl.library.utils.config.BasicConfig;
|
||||
|
||||
/**
|
||||
* <p> 网络判断工具类</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2017/12/4
|
||||
*/
|
||||
public class RxNetWorkUtils {
|
||||
/**
|
||||
* 判断网络是否可用
|
||||
* <p>需添加权限 {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>}</p>
|
||||
*
|
||||
* @param context 上下文
|
||||
* @return {@code true}: 可用<br>{@code false}: 不可用
|
||||
*/
|
||||
public static boolean isAvailable(Context context) {
|
||||
NetworkInfo info = getActiveNetworkInfo(BasicConfig.INSTANCE.getAppContext());
|
||||
return info != null && info.isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动网络信息
|
||||
*
|
||||
* @param context 上下文
|
||||
* @return NetworkInfo
|
||||
*/
|
||||
public static NetworkInfo getActiveNetworkInfo(Context context) {
|
||||
ConnectivityManager cm = (ConnectivityManager) BasicConfig.INSTANCE.getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm.getActiveNetworkInfo();
|
||||
}
|
||||
}
|
@@ -0,0 +1,139 @@
|
||||
package com.chwl.library.record;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Handler;
|
||||
|
||||
public class AuditRecorderConfiguration {
|
||||
|
||||
public static final int[] SAMPLE_RATES = {44100, 22050, 11025, 8000};
|
||||
public static final boolean RECORDING_UNCOMPRESSED = true;
|
||||
public static final boolean RECORDING_COMPRESSED = false;
|
||||
|
||||
private ExtAudioRecorder.RecorderListener listener;
|
||||
private boolean uncompressed;
|
||||
private int timerInterval;
|
||||
private int rate;
|
||||
private int source;
|
||||
private int channelConfig;
|
||||
private int format;
|
||||
private Handler handler;
|
||||
|
||||
/**
|
||||
* 创建一个默认的配置 <br />
|
||||
* <ul>
|
||||
* <li>uncompressed = false</li>
|
||||
* <li>timerInterval = 120</li>
|
||||
* <li>rate = 8000</li>
|
||||
* <li>source = {@link MediaRecorder.AudioSource#MIC}</li>
|
||||
* <li>channelConfig = {@link AudioFormat#CHANNEL_CONFIGURATION_MONO}</li>
|
||||
* <li>format = {@link AudioFormat#ENCODING_PCM_16BIT}</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
public static AuditRecorderConfiguration createDefaule(){
|
||||
return new Builder().builder();
|
||||
}
|
||||
|
||||
|
||||
public ExtAudioRecorder.RecorderListener getRecorderListener(){
|
||||
return listener;
|
||||
}
|
||||
|
||||
public boolean isUncompressed(){
|
||||
return uncompressed;
|
||||
}
|
||||
|
||||
public int getTimerInterval(){
|
||||
return timerInterval;
|
||||
}
|
||||
|
||||
public int getRate(){
|
||||
return rate;
|
||||
}
|
||||
|
||||
|
||||
public int getSource(){
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getFormat(){
|
||||
return format;
|
||||
}
|
||||
|
||||
public Handler getHandler(){
|
||||
return handler;
|
||||
}
|
||||
|
||||
public int getChannelConfig(){
|
||||
return channelConfig;
|
||||
}
|
||||
|
||||
private AuditRecorderConfiguration(Builder builder){
|
||||
this.listener = builder.listener;
|
||||
this.uncompressed = builder.uncompressed;
|
||||
this.timerInterval = builder.timerInterval;
|
||||
this.rate = builder.rate;
|
||||
this.source = builder.source;
|
||||
this.format = builder.format;
|
||||
this.handler = builder.handler;
|
||||
this.channelConfig = builder.channelConfig;
|
||||
}
|
||||
|
||||
public static class Builder{
|
||||
private ExtAudioRecorder.RecorderListener listener;
|
||||
private boolean uncompressed;
|
||||
private int timerInterval = 120;
|
||||
private int rate = SAMPLE_RATES[3];
|
||||
private int source = MediaRecorder.AudioSource.MIC;
|
||||
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
|
||||
private int format = AudioFormat.ENCODING_PCM_16BIT;
|
||||
private Handler handler;
|
||||
|
||||
/** 声道设置 */
|
||||
public Builder getChannelConfig(int channelConfig){
|
||||
this.channelConfig = channelConfig;
|
||||
return this;
|
||||
}
|
||||
/** 录音失败的监听 */
|
||||
public Builder recorderListener(ExtAudioRecorder.RecorderListener listener){
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
/** 是否压缩录音 */
|
||||
public Builder uncompressed(boolean uncompressed){
|
||||
this.uncompressed = uncompressed;
|
||||
return this;
|
||||
}
|
||||
/** 周期的时间间隔 */
|
||||
public Builder timerInterval(int timeInterval){
|
||||
timerInterval = timeInterval;
|
||||
return this;
|
||||
}
|
||||
/** 采样率 */
|
||||
public Builder rate(int rate){
|
||||
this.rate = rate;
|
||||
return this;
|
||||
}
|
||||
/** 音频源 */
|
||||
public Builder source(int source){
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
/** 编码制式和采样大小 */
|
||||
public Builder format(int format){
|
||||
this.format = format;
|
||||
return this;
|
||||
}
|
||||
/** 返回what是振幅值 1-13 */
|
||||
public Builder handler(Handler handler){
|
||||
this.handler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuditRecorderConfiguration builder(){
|
||||
return new AuditRecorderConfiguration(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,514 @@
|
||||
package com.chwl.library.record;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Date;
|
||||
|
||||
public class ExtAudioRecorder {
|
||||
|
||||
private AuditRecorderConfiguration configuration;
|
||||
|
||||
public interface RecorderListener {
|
||||
void recordFailed(FailRecorder failRecorder);
|
||||
}
|
||||
|
||||
public ExtAudioRecorder(AuditRecorderConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
|
||||
if (configuration.isUncompressed()) {
|
||||
init(configuration.isUncompressed(),
|
||||
configuration.getSource(),
|
||||
configuration.getRate(),
|
||||
configuration.getChannelConfig(),
|
||||
configuration.getFormat());
|
||||
} else {
|
||||
int i = 0;
|
||||
do {
|
||||
init(configuration.isUncompressed(),
|
||||
configuration.getSource(),
|
||||
AuditRecorderConfiguration.SAMPLE_RATES[i],
|
||||
configuration.getChannelConfig(),
|
||||
configuration.getFormat());
|
||||
|
||||
}
|
||||
while ((++i < AuditRecorderConfiguration.SAMPLE_RATES.length) & !(getState() == ExtAudioRecorder.State.INITIALIZING));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 录音的状态
|
||||
*/
|
||||
public enum State {
|
||||
/**
|
||||
* 录音初始化
|
||||
*/
|
||||
INITIALIZING,
|
||||
/**
|
||||
* 已准备好录音
|
||||
*/
|
||||
READY,
|
||||
/**
|
||||
* 录音中
|
||||
*/
|
||||
RECORDING,
|
||||
/**
|
||||
* 录音生了错误
|
||||
*/
|
||||
ERROR,
|
||||
/**
|
||||
* 停止录音
|
||||
*/
|
||||
STOPPED
|
||||
}
|
||||
|
||||
// 不压缩将使用这个进行录音
|
||||
private AudioRecord audioRecorder = null;
|
||||
|
||||
// 压缩将使用这进行录音
|
||||
private MediaRecorder mediaRecorder = null;
|
||||
|
||||
// 当前的振幅 (只有在未压缩的模式下)
|
||||
private int cAmplitude = 0;
|
||||
|
||||
// 录音状态
|
||||
private State state;
|
||||
|
||||
// 文件 (只有在未压缩的模式下)
|
||||
private RandomAccessFile randomAccessWriter;
|
||||
|
||||
private int bufferSize;
|
||||
|
||||
// 录音 通知周期(只有在未压缩的模式下)
|
||||
private int framePeriod;
|
||||
// 输出的字节(只有在未压缩的模式下)
|
||||
private byte[] buffer;
|
||||
|
||||
private short samples;
|
||||
private short channels;
|
||||
|
||||
// 写入头文件的字节数(只有在未压缩的模式下)
|
||||
// after stop() is called, this size is written to the header/data chunk in
|
||||
// the wave file
|
||||
private int payloadSize;
|
||||
//录音的开始时间
|
||||
private long startTime;
|
||||
|
||||
private String filePath;
|
||||
|
||||
/**
|
||||
* 返回录音的状态
|
||||
*
|
||||
* @return 录音的状态
|
||||
*/
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Method used for recording.
|
||||
*/
|
||||
private AudioRecord.OnRecordPositionUpdateListener updateListener = new AudioRecord.OnRecordPositionUpdateListener() {
|
||||
public void onPeriodicNotification(AudioRecord recorder) {
|
||||
audioRecorder.read(buffer, 0, buffer.length); // Fill buffer
|
||||
try {
|
||||
randomAccessWriter.write(buffer); // Write buffer to file
|
||||
payloadSize += buffer.length;
|
||||
if (samples == 16) {
|
||||
for (int i = 0; i < buffer.length / 2; i++) { // 16bit sample size
|
||||
short curSample = getShort(buffer[i * 2], buffer[i * 2 + 1]);
|
||||
if (curSample > cAmplitude) { // Check amplitude
|
||||
cAmplitude = curSample;
|
||||
}
|
||||
}
|
||||
} else { // 8bit sample size
|
||||
for (int i = 0; i < buffer.length; i++) {
|
||||
if (buffer[i] > cAmplitude) { // Check amplitude
|
||||
cAmplitude = buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(ExtAudioRecorder.class.getName(), "Error occured in updateListener, recording is aborted");
|
||||
//stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void onMarkerReached(AudioRecord recorder) {
|
||||
// NOT USED
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 默认的构造方法,如果压缩录音,剩下的参数可以为0.这个方法不会抛出异常,但是会设置状态为 {@link State#ERROR}
|
||||
*
|
||||
* @param uncompressed 是否压缩录音 true不压缩,false压缩
|
||||
* @param audioSource 音频源:指的是从哪里采集音频。通过 {@link AudioRecord} 的一些常量去设置
|
||||
* @param sampleRate 采样率:音频的采样频率,每秒钟能够采样的次数,采样率越高,音质越高。给出的实例是44100、22050、11025但不限于这几个参数。
|
||||
* 例如要采集低质量的音频就可以使用4000、8000等低采样率。
|
||||
* @param channelConfig 声道设置:Android支持双声道立体声和单声道。MONO单声道,STEREO立体声
|
||||
* @param audioFormat 编码制式和采样大小:采集来的数据当然使用PCM编码(脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。)
|
||||
* android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit足够了。
|
||||
*/
|
||||
public void init(boolean uncompressed, int audioSource, int sampleRate, int channelConfig, int audioFormat) {
|
||||
try {
|
||||
if (uncompressed) { // RECORDING_UNCOMPRESSED
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
|
||||
samples = 16;
|
||||
} else {
|
||||
samples = 8;
|
||||
}
|
||||
|
||||
if (channelConfig == AudioFormat.CHANNEL_CONFIGURATION_MONO) {
|
||||
channels = 1;
|
||||
} else {
|
||||
channels = 2;
|
||||
}
|
||||
|
||||
framePeriod = sampleRate * configuration.getTimerInterval() / 1000;
|
||||
bufferSize = framePeriod * 2 * samples * channels / 8;
|
||||
if (bufferSize < AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)) {
|
||||
// Check to make sure
|
||||
// buffer size is not
|
||||
// smaller than the
|
||||
// smallest allowed one
|
||||
bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
// Set frame period and timer interval accordingly
|
||||
framePeriod = bufferSize / (2 * samples * channels / 8);
|
||||
Log.w(ExtAudioRecorder.class.getName(), "Increasing buffer size to " + Integer.toString(bufferSize));
|
||||
}
|
||||
|
||||
audioRecorder = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSize);
|
||||
|
||||
if (audioRecorder.getState() != AudioRecord.STATE_INITIALIZED)
|
||||
throw new Exception("AudioRecord initialization failed");
|
||||
audioRecorder.setRecordPositionUpdateListener(updateListener);
|
||||
audioRecorder.setPositionNotificationPeriod(framePeriod);
|
||||
} else { // RECORDING_COMPRESSED
|
||||
mediaRecorder = new MediaRecorder();
|
||||
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
|
||||
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
|
||||
}
|
||||
cAmplitude = 0;
|
||||
filePath = null;
|
||||
state = State.INITIALIZING;
|
||||
} catch (Exception e) {
|
||||
fireFailEvent(FailRecorder.FailType.NO_PERMISSION, e);
|
||||
if (e.getMessage() != null) {
|
||||
Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while initializing recording");
|
||||
}
|
||||
state = State.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输出的文件路径
|
||||
*
|
||||
* @param argPath 文件路径
|
||||
*/
|
||||
public void setOutputFile(String argPath) {
|
||||
try {
|
||||
if (state == State.INITIALIZING) {
|
||||
filePath = argPath;
|
||||
if (!configuration.isUncompressed()) {
|
||||
mediaRecorder.setOutputFile(filePath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage() != null) {
|
||||
Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(),
|
||||
"Unknown error occured while setting output path");
|
||||
}
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the largest amplitude sampled since the last call to this method.
|
||||
*
|
||||
* @return returns the largest amplitude since the last call, or 0 when not
|
||||
* in recording state.
|
||||
*/
|
||||
public int getMaxAmplitude() {
|
||||
if (state == State.RECORDING) {
|
||||
if (configuration.isUncompressed()) {
|
||||
int result = cAmplitude;
|
||||
cAmplitude = 0;
|
||||
return result;
|
||||
} else {
|
||||
try {
|
||||
return mediaRecorder.getMaxAmplitude();
|
||||
} catch (IllegalStateException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备录音的录音机, 如果 state 不是 {@link State#INITIALIZING} 或文件路径为null
|
||||
* 将设置 state 为 {@link State#ERROR}。如果发生异常不会抛出,而是设置 state 为
|
||||
* {@link State#ERROR}
|
||||
*/
|
||||
public void prepare() {
|
||||
try {
|
||||
if (state == State.INITIALIZING) {
|
||||
if (configuration.isUncompressed()) {
|
||||
if ((audioRecorder.getState() == AudioRecord.STATE_INITIALIZED) & (filePath != null)) {
|
||||
// 写文件头
|
||||
randomAccessWriter = new RandomAccessFile(filePath, "rw");
|
||||
//设置文件长度为0,为了防止这个file以存在
|
||||
randomAccessWriter.setLength(0);
|
||||
randomAccessWriter.writeBytes("RIFF");
|
||||
//不知道文件最后的大小,所以设置0
|
||||
randomAccessWriter.writeInt(0);
|
||||
randomAccessWriter.writeBytes("WAVE");
|
||||
randomAccessWriter.writeBytes("fmt ");
|
||||
// Sub-chunk
|
||||
// size,
|
||||
// 16
|
||||
// for
|
||||
// PCM
|
||||
randomAccessWriter.writeInt(Integer.reverseBytes(16));
|
||||
// AudioFormat, 1 为 PCM
|
||||
randomAccessWriter.writeShort(Short.reverseBytes((short) 1));
|
||||
// 数字为声道, 1 为 mono, 2 为 stereo
|
||||
randomAccessWriter.writeShort(Short.reverseBytes(channels));
|
||||
// 采样率
|
||||
randomAccessWriter.writeInt(Integer.reverseBytes(configuration.getRate()));
|
||||
// 采样率, SampleRate*NumberOfChannels*BitsPerSample/8
|
||||
randomAccessWriter.writeInt(Integer.reverseBytes(configuration.getRate() * samples * channels / 8));
|
||||
randomAccessWriter.writeShort(Short.reverseBytes((short) (channels * samples / 8)));
|
||||
// Block
|
||||
// align,
|
||||
// NumberOfChannels*BitsPerSample/8
|
||||
randomAccessWriter.writeShort(Short.reverseBytes(samples)); // Bits per sample
|
||||
randomAccessWriter.writeBytes("data");
|
||||
randomAccessWriter.writeInt(0); // Data chunk size not
|
||||
// known yet, write 0
|
||||
|
||||
buffer = new byte[framePeriod * samples / 8 * channels];
|
||||
state = State.READY;
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(),
|
||||
"prepare() method called on uninitialized recorder");
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
|
||||
}
|
||||
} else {
|
||||
mediaRecorder.prepare();
|
||||
state = State.READY;
|
||||
}
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on illegal state");
|
||||
release();
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage() != null) {
|
||||
Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured in prepare()");
|
||||
}
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放与这个类相关的资源,和移除不必要的文件,在必要的时候
|
||||
*/
|
||||
public void release() {
|
||||
if (state == State.RECORDING) {
|
||||
stop();
|
||||
} else {
|
||||
if ((state == State.READY) & (configuration.isUncompressed())) {
|
||||
try {
|
||||
randomAccessWriter.close(); // 删除准备文件
|
||||
} catch (IOException e) {
|
||||
Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file");
|
||||
}
|
||||
(new File(filePath)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.isUncompressed()) {
|
||||
if (audioRecorder != null) {
|
||||
audioRecorder.release();
|
||||
}
|
||||
} else {
|
||||
if (mediaRecorder != null) {
|
||||
mediaRecorder.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void discardRecording() {
|
||||
stop();
|
||||
|
||||
File file = new File(filePath);
|
||||
if (file.exists() && !file.isDirectory()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置录音,并设置 state 为 {@link State#INITIALIZING},如果当前状态为 {@link State#RECORDING},将会停止录音。
|
||||
* 这个方法不会抛出异常,但是会设置状态为 {@link State#ERROR}
|
||||
*/
|
||||
public void reset() {
|
||||
try {
|
||||
if (state != State.ERROR) {
|
||||
release();
|
||||
filePath = null; // Reset file path
|
||||
cAmplitude = 0; // Reset amplitude
|
||||
if (configuration.isUncompressed()) {
|
||||
audioRecorder = new AudioRecord(configuration.getSource(), configuration.getRate(),
|
||||
channels + 1, configuration.getFormat(), bufferSize);
|
||||
audioRecorder.setRecordPositionUpdateListener(updateListener);
|
||||
audioRecorder.setPositionNotificationPeriod(framePeriod);
|
||||
} else {
|
||||
mediaRecorder = new MediaRecorder();
|
||||
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
mediaRecorder
|
||||
.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
|
||||
mediaRecorder
|
||||
.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
|
||||
}
|
||||
state = State.INITIALIZING;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始录音,并设置 state 为 {@link State#RECORDING}。在调用这个方法前必须调用 {@link ExtAudioRecorder#prepare()} 方法
|
||||
*/
|
||||
public void start() {
|
||||
if (state == State.READY) {
|
||||
if (configuration.isUncompressed()) {
|
||||
payloadSize = 0;
|
||||
audioRecorder.startRecording();
|
||||
audioRecorder.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
mediaRecorder.start();
|
||||
}
|
||||
state = State.RECORDING;
|
||||
this.startTime = (new Date()).getTime();
|
||||
startGetMaxAmplitudeThread();
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(), "start() called on illegal state");
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止录音,并设置 state 为 {@link State#STOPPED}。如果要继续使用,则需要调用 {@link #reset()} 方法
|
||||
*
|
||||
* @return 录音的时间
|
||||
*/
|
||||
public int stop() {
|
||||
if (state == State.RECORDING) {
|
||||
if (configuration.isUncompressed()) {
|
||||
audioRecorder.stop();
|
||||
|
||||
try {
|
||||
randomAccessWriter.seek(4); // Write size to RIFF header
|
||||
randomAccessWriter.writeInt(Integer.reverseBytes(36 + payloadSize));
|
||||
|
||||
randomAccessWriter.seek(40); // Write size to Subchunk2Size
|
||||
// field
|
||||
randomAccessWriter.writeInt(Integer.reverseBytes(payloadSize));
|
||||
|
||||
randomAccessWriter.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(ExtAudioRecorder.class.getName(),
|
||||
"I/O exception occured while closing output file");
|
||||
state = State.ERROR;
|
||||
}
|
||||
} else {
|
||||
try{
|
||||
mediaRecorder.stop();
|
||||
} catch (Exception e){}
|
||||
}
|
||||
state = State.STOPPED;
|
||||
|
||||
File file = new File(filePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
if (file.length() == 0L) {
|
||||
file.delete();
|
||||
return 0;
|
||||
} else {
|
||||
int time = (int) ((new Date()).getTime() - this.startTime) / 1000;
|
||||
return time;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
Log.e(ExtAudioRecorder.class.getName(), "stop() called on illegal state");
|
||||
state = State.ERROR;
|
||||
fireFailEvent(FailRecorder.FailType.UNKNOWN, null);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void startGetMaxAmplitudeThread() {
|
||||
if (configuration.getHandler() != null) {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (true) {
|
||||
if (state == State.RECORDING) {
|
||||
Message var1 = new Message();
|
||||
var1.what = getMaxAmplitude() * 13 / 32767;
|
||||
configuration.getHandler().sendMessage(var1);
|
||||
SystemClock.sleep(100L);
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
/** Converts a byte[2] to a short, in LITTLE_ENDIAN format */
|
||||
private short getShort(byte argB1, byte argB2) {
|
||||
return (short) (argB1 | (argB2 << 8));
|
||||
}
|
||||
|
||||
private void fireFailEvent(final FailRecorder.FailType failType, final Throwable failCause) {
|
||||
|
||||
if (configuration.getRecorderListener() != null) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
configuration.getRecorderListener().recordFailed(new FailRecorder(failType, failCause));
|
||||
}
|
||||
};
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.chwl.library.record;
|
||||
|
||||
public class FailRecorder {
|
||||
private final FailType type;
|
||||
|
||||
private final Throwable cause;
|
||||
|
||||
public FailRecorder(FailType type, Throwable cause) {
|
||||
this.type = type;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public FailType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
public enum FailType {
|
||||
/** 没有权限 */
|
||||
NO_PERMISSION,
|
||||
/** 位置异常 */
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package com.chwl.library.record;
|
||||
|
||||
public class SimpleRecordFailed implements ExtAudioRecorder.RecorderListener{
|
||||
@Override
|
||||
public void recordFailed(FailRecorder failRecorder) {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.chwl.library.rx;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.functions.Function;
|
||||
|
||||
/**
|
||||
* Created by MadisonRong on 13/04/2018.
|
||||
*/
|
||||
|
||||
public class RxRetryWithDelay implements Function<Flowable<Throwable>, Publisher<?>> {
|
||||
|
||||
private static final String TAG = "RxRetryWithDelay";
|
||||
|
||||
private final int maxRetries;
|
||||
private final int retryDelayMillis;
|
||||
private int retryCount;
|
||||
|
||||
public RxRetryWithDelay(final int maxRetries, final int retryDelayMillis) {
|
||||
this.maxRetries = maxRetries;
|
||||
this.retryDelayMillis = retryDelayMillis;
|
||||
this.retryCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<?> apply(Flowable<Throwable> throwableFlowable) throws Exception {
|
||||
return throwableFlowable.flatMap(new Function<Throwable, Publisher<?>>() {
|
||||
@Override
|
||||
public Publisher<?> apply(Throwable throwable) throws Exception {
|
||||
Log.e(TAG, String.format(Locale.getDefault(), "maxRetries: %s, retryCount: %s",
|
||||
maxRetries, retryCount));
|
||||
if (++retryCount < maxRetries) {
|
||||
return Flowable.timer(retryDelayMillis,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
return Flowable.error(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
61
library/src/main/java/com/chwl/library/rxbus/RxBus.java
Normal file
61
library/src/main/java/com/chwl/library/rxbus/RxBus.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.chwl.library.rxbus;
|
||||
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.processors.FlowableProcessor;
|
||||
import io.reactivex.processors.PublishProcessor;
|
||||
import io.reactivex.subscribers.SerializedSubscriber;
|
||||
|
||||
/**
|
||||
* <p> 基于RxJava的事件分发封装</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/12
|
||||
*/
|
||||
public class RxBus {
|
||||
private final FlowableProcessor<Object> mBus;
|
||||
|
||||
private static class Holder {
|
||||
private static final RxBus BUS = new RxBus();
|
||||
}
|
||||
private RxBus() {
|
||||
//toSerialized()保证线程安全
|
||||
mBus = PublishProcessor.create().toSerialized();
|
||||
}
|
||||
|
||||
public static RxBus get() {
|
||||
return Holder.BUS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param o
|
||||
*/
|
||||
public void post(Object o) {
|
||||
new SerializedSubscriber<>(mBus).onNext(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定接收消息类型
|
||||
*
|
||||
* @param tClass 消息类型
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> Flowable<T> toFlowable(Class<T> tClass) {
|
||||
return mBus.ofType(tClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the subject has subscribers.
|
||||
* <p>The method is thread-safe.
|
||||
*
|
||||
* @return true if the subject has subscribers
|
||||
*/
|
||||
public boolean hasSubscribers() {
|
||||
return mBus.hasSubscribers();
|
||||
}
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
package com.chwl.library.rxbus;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* <p> {@link RxBus}简单封装,支持在主线程接收,子线程接收 </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2017/12/12
|
||||
*/
|
||||
public class RxBusHelper {
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param o
|
||||
*/
|
||||
public static void post(Object o) {
|
||||
RxBus.get().post(o);
|
||||
}
|
||||
|
||||
private static <T> void doReceiveEvent(Class<T> tClass, CompositeDisposable disposable,
|
||||
Consumer<T> onNext, Consumer<Throwable> onError, boolean isMainThread) {
|
||||
if (disposable != null && onError != null) {
|
||||
disposable.add(RxBus.get().toFlowable(tClass)
|
||||
.subscribeOn(isMainThread ? AndroidSchedulers.mainThread() : Schedulers.newThread())
|
||||
.unsubscribeOn(isMainThread ? AndroidSchedulers.mainThread() : Schedulers.newThread())
|
||||
.observeOn(isMainThread ? AndroidSchedulers.mainThread() : Schedulers.newThread())
|
||||
.subscribe(onNext, onError)
|
||||
);
|
||||
} else {
|
||||
if (disposable != null) {
|
||||
disposable.add(RxBus.get().toFlowable(tClass)
|
||||
// .compose(RxBus.get().mContext.<T>bindUntilEvent(ActivityEvent.DESTROY))
|
||||
.subscribeOn(isMainThread ? AndroidSchedulers.mainThread() : Schedulers.newThread())
|
||||
.unsubscribeOn(isMainThread ? AndroidSchedulers.mainThread() : Schedulers.newThread())
|
||||
.observeOn(isMainThread ? AndroidSchedulers.mainThread() : Schedulers.newThread())
|
||||
.subscribe(onNext)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息,且在主线程处理
|
||||
*
|
||||
* @param tClass 接收消息类型
|
||||
* @param disposable 存放消息
|
||||
* @param onNext 处理成功结果回调
|
||||
* @param onError 处理错误结果回调
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T> void doOnMainThread(Class<T> tClass, CompositeDisposable disposable,
|
||||
Consumer<T> onNext, Consumer<Throwable> onError) {
|
||||
doReceiveEvent(tClass, disposable, onNext, onError, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息,且在主线程处理
|
||||
*
|
||||
* @param tClass 接收消息类型
|
||||
* @param disposable 存放消息
|
||||
* @param onNext 处理成功结果回调
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T> void doOnMainThread(Class<T> tClass, CompositeDisposable disposable, Consumer<T> onNext) {
|
||||
doOnMainThread(tClass, disposable, onNext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息,且在子线程处理
|
||||
*
|
||||
* @param tClass 接收消息类型
|
||||
* @param disposable 存放消息
|
||||
* @param onNext 处理成功结果回调
|
||||
* @param onError 处理错误结果回调
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T> void doOnChildThread(Class<T> tClass, CompositeDisposable disposable,
|
||||
Consumer<T> onNext, Consumer<Throwable> onError) {
|
||||
doReceiveEvent(tClass, disposable, onNext, onError, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息,且在子线程处理
|
||||
*
|
||||
* @param tClass 接收消息类型
|
||||
* @param disposable 存放消息
|
||||
* @param onNext 处理成功结果回调
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T> void doOnChildThread(Class<T> tClass, CompositeDisposable disposable, Consumer<T> onNext) {
|
||||
doOnChildThread(tClass, disposable, onNext, null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.chwl.library.service;
|
||||
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.orhanobut.logger.Logger;
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
/**
|
||||
* <p> 在电量充足,wifi等情况下处理后台耗时任务</p>
|
||||
*
|
||||
* @author jiahui
|
||||
* date 2018/3/6
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public class ErBanService extends JobService {
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
int jobId = params.getJobId();
|
||||
Logger.d(ResUtil.getString(R.string.xchat_android_library_service_erbanservice_01), jobId);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
int jobId = params.getJobId();
|
||||
Logger.d(ResUtil.getString(R.string.xchat_android_library_service_erbanservice_05), jobId);
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package com.chwl.library.softinput;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class SoftHideKeyBoardUtil {
|
||||
private View mChildOfContent;
|
||||
private int usableHeightPrevious;
|
||||
private FrameLayout.LayoutParams frameLayoutParams;
|
||||
|
||||
private SoftHideKeyBoardUtil(Activity activity) {
|
||||
//找到DecorView
|
||||
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
|
||||
//获取到用户设置的View
|
||||
mChildOfContent = content.getChildAt(0);
|
||||
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
possiblyResizeChildOfContent();
|
||||
}
|
||||
});
|
||||
|
||||
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
|
||||
}
|
||||
|
||||
private void possiblyResizeChildOfContent() {
|
||||
int usableHeightNow = computeUsableHeight();
|
||||
|
||||
if (usableHeightNow != usableHeightPrevious) {
|
||||
int usableHeightSansKeyBoard = mChildOfContent.getRootView().getHeight();
|
||||
//Activity中xml布局的高度
|
||||
int heightDifference = usableHeightSansKeyBoard - usableHeightNow;
|
||||
if (heightDifference > 100 /*(usableHeightSansKeyBoard / 4)*/) {
|
||||
frameLayoutParams.height = usableHeightSansKeyBoard - heightDifference;
|
||||
} else {
|
||||
frameLayoutParams.height = usableHeightSansKeyBoard;
|
||||
}
|
||||
|
||||
mChildOfContent.requestLayout();
|
||||
usableHeightPrevious = usableHeightNow;
|
||||
}
|
||||
}
|
||||
|
||||
private int computeUsableHeight() {
|
||||
Rect rect = new Rect();
|
||||
mChildOfContent.getWindowVisibleDisplayFrame(rect);
|
||||
//全屏时,直接返回rect.bottom,rect.top是状态栏的高度
|
||||
// return (rect.bottom - rect.top);
|
||||
return rect.bottom;
|
||||
}
|
||||
|
||||
public static void assistActivity(Activity activity) {
|
||||
new SoftHideKeyBoardUtil(activity);
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
|
||||
package com.chwl.library.swipeactivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Created by hm
|
||||
*/
|
||||
public class SwipeActivityUtils {
|
||||
private SwipeActivityUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a translucent themed Activity
|
||||
* {@link android.R.attr#windowIsTranslucent} to a fullscreen opaque
|
||||
* Activity.
|
||||
* <p>
|
||||
* Call this whenever the background of a translucent Activity has changed
|
||||
* to become opaque. Doing so will allow the {@link android.view.Surface} of
|
||||
* the Activity behind to be released.
|
||||
* <p>
|
||||
* This call has no effect on non-translucent activities or on activities
|
||||
* with the {@link android.R.attr#windowIsFloating} attribute.
|
||||
*/
|
||||
public static void convertActivityFromTranslucent(Activity activity) {
|
||||
try {
|
||||
Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
|
||||
method.setAccessible(true);
|
||||
method.invoke(activity);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a translucent themed Activity
|
||||
* {@link android.R.attr#windowIsTranslucent} back from opaque to
|
||||
* translucent following a call to
|
||||
* {@link #convertActivityFromTranslucent(Activity)} .
|
||||
* <p>
|
||||
* Calling this allows the Activity behind this one to be seen again. Once
|
||||
* all such Activities have been redrawn
|
||||
* <p>
|
||||
* This call has no effect on non-translucent activities or on activities
|
||||
* with the {@link android.R.attr#windowIsFloating} attribute.
|
||||
*/
|
||||
public static void convertActivityToTranslucent(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
convertActivityToTranslucentAfterL(activity);
|
||||
} else {
|
||||
convertActivityToTranslucentBeforeL(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling the convertToTranslucent method on platforms before Android 5.0
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void convertActivityToTranslucentBeforeL(Activity activity) {
|
||||
try {
|
||||
Class<?>[] classes = Activity.class.getDeclaredClasses();
|
||||
Class<?> translucentConversionListenerClazz = null;
|
||||
for (Class clazz : classes) {
|
||||
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
|
||||
translucentConversionListenerClazz = clazz;
|
||||
}
|
||||
}
|
||||
Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
|
||||
translucentConversionListenerClazz);
|
||||
method.setAccessible(true);
|
||||
method.invoke(activity, new Object[] {
|
||||
null
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling the convertToTranslucent method on platforms after Android 5.0
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static void convertActivityToTranslucentAfterL(Activity activity) {
|
||||
try {
|
||||
Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
|
||||
getActivityOptions.setAccessible(true);
|
||||
Object options = getActivityOptions.invoke(activity);
|
||||
|
||||
Class<?>[] classes = Activity.class.getDeclaredClasses();
|
||||
Class<?> translucentConversionListenerClazz = null;
|
||||
for (Class clazz : classes) {
|
||||
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
|
||||
translucentConversionListenerClazz = clazz;
|
||||
}
|
||||
}
|
||||
Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
|
||||
translucentConversionListenerClazz, ActivityOptions.class);
|
||||
convertToTranslucent.setAccessible(true);
|
||||
convertToTranslucent.invoke(activity, null, options);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
|
||||
package com.chwl.library.swipeactivity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.trello.rxlifecycle3.components.support.RxAppCompatActivity;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* <p>Title: SwipeBackActivity</p>
|
||||
* <p>Description: 滑动退出
|
||||
*
|
||||
* @author hm
|
||||
* @version V1.0<p>
|
||||
* @Date 2017年5月24日 下午5:05:13
|
||||
* 修改记录:
|
||||
* 下面填写修改的内容以及修改的日期
|
||||
*
|
||||
*/
|
||||
public class SwipeBackActivity extends RxAppCompatActivity implements SwipeBackActivityBase {
|
||||
private SwipeBackActivityHelper mHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mHelper = new SwipeBackActivityHelper(this);
|
||||
mHelper.onActivityCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
mHelper.onPostCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View findViewById(int id) {
|
||||
View v = super.findViewById(id);
|
||||
if (v == null && mHelper != null)
|
||||
return mHelper.findViewById(id);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// fixInputMethodManagerLeak(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SwipeBackLayout getSwipeBackLayout() {
|
||||
return mHelper.getSwipeBackLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSwipeBackEnable(boolean enable) {
|
||||
getSwipeBackLayout().setEnableGesture(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToFinishActivity() {
|
||||
SwipeActivityUtils.convertActivityToTranslucent(this);
|
||||
getSwipeBackLayout().scrollToFinishActivity();
|
||||
}
|
||||
|
||||
public static void fixInputMethodManagerLeak(Context destContext) {
|
||||
if (destContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String [] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
|
||||
Field f = null;
|
||||
Object obj_get = null;
|
||||
for (int i = 0;i < arr.length;i ++) {
|
||||
String param = arr[i];
|
||||
try{
|
||||
f = imm.getClass().getDeclaredField(param);
|
||||
if (f.isAccessible() == false) {
|
||||
f.setAccessible(true);
|
||||
}
|
||||
obj_get = f.get(imm);
|
||||
if (obj_get != null && obj_get instanceof View) {
|
||||
View v_get = (View) obj_get;
|
||||
if (v_get.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的
|
||||
f.set(imm, null); // 置空,破坏掉path to gc节点
|
||||
} else {
|
||||
// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了
|
||||
break;
|
||||
}
|
||||
}
|
||||
}catch(Throwable t){
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.chwl.library.swipeactivity;
|
||||
|
||||
/**
|
||||
* @author hm
|
||||
*/
|
||||
public interface SwipeBackActivityBase {
|
||||
/**
|
||||
* 得到SwipeBackLayout对象
|
||||
* @return
|
||||
*/
|
||||
public abstract SwipeBackLayout getSwipeBackLayout();
|
||||
|
||||
/**
|
||||
* 设置是否可以滑动
|
||||
* @param enable
|
||||
*/
|
||||
public abstract void setSwipeBackEnable(boolean enable);
|
||||
|
||||
/**
|
||||
* 自动滑动返回并关闭Activity
|
||||
*/
|
||||
public abstract void scrollToFinishActivity();
|
||||
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package com.chwl.library.swipeactivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.chwl.library.R;
|
||||
|
||||
/**
|
||||
* @author Yrom
|
||||
*/
|
||||
public class SwipeBackActivityHelper {
|
||||
private Activity mActivity;
|
||||
|
||||
private SwipeBackLayout mSwipeBackLayout;
|
||||
|
||||
public SwipeBackActivityHelper(Activity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onActivityCreate() {
|
||||
mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
mActivity.getWindow().getDecorView().setBackgroundDrawable(null);
|
||||
mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(R.layout.swipeback_layout, null);
|
||||
mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() {
|
||||
@Override
|
||||
public void onScrollStateChange(int state, float scrollPercent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEdgeTouch(int edgeFlag) {
|
||||
SwipeActivityUtils.convertActivityToTranslucent(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollOverThreshold() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onPostCreate() {
|
||||
mSwipeBackLayout.attachToActivity(mActivity);
|
||||
}
|
||||
|
||||
public View findViewById(int id) {
|
||||
if (mSwipeBackLayout != null) {
|
||||
return mSwipeBackLayout.findViewById(id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SwipeBackLayout getSwipeBackLayout() {
|
||||
return mSwipeBackLayout;
|
||||
}
|
||||
}
|
@@ -0,0 +1,611 @@
|
||||
package com.chwl.library.swipeactivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import com.chwl.library.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SwipeBackLayout extends FrameLayout {
|
||||
/**
|
||||
* Minimum velocity that will be detected as a fling
|
||||
*/
|
||||
private static final int MIN_FLING_VELOCITY = 400; // dips per second
|
||||
|
||||
private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
|
||||
|
||||
private static final int FULL_ALPHA = 255;
|
||||
|
||||
/**
|
||||
* Edge flag indicating that the left edge should be affected.
|
||||
*/
|
||||
public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT;
|
||||
|
||||
/**
|
||||
* Edge flag indicating that the right edge should be affected.
|
||||
*/
|
||||
public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT;
|
||||
|
||||
/**
|
||||
* Edge flag indicating that the bottom edge should be affected.
|
||||
*/
|
||||
public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM;
|
||||
|
||||
/**
|
||||
* Edge flag set indicating all edges should be affected.
|
||||
*/
|
||||
public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM;
|
||||
|
||||
/**
|
||||
* A view is not currently being dragged or animating as a result of a
|
||||
* fling/snap.
|
||||
*/
|
||||
public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
|
||||
|
||||
/**
|
||||
* A view is currently being dragged. The position is currently changing as
|
||||
* a result of user input or simulated user input.
|
||||
*/
|
||||
public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
|
||||
|
||||
/**
|
||||
* A view is currently settling into place as a result of a fling or
|
||||
* predefined non-interactive motion.
|
||||
*/
|
||||
public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
|
||||
|
||||
/**
|
||||
* Default threshold of scroll
|
||||
*/
|
||||
private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f;
|
||||
|
||||
private static final int OVERSCROLL_DISTANCE = 10;
|
||||
|
||||
private static final int[] EDGE_FLAGS = {
|
||||
EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL
|
||||
};
|
||||
|
||||
private int mEdgeFlag;
|
||||
|
||||
/**
|
||||
* Threshold of scroll, we will close the activity, when scrollPercent over
|
||||
* this value;
|
||||
*/
|
||||
private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD;
|
||||
|
||||
private Activity mActivity;
|
||||
|
||||
private boolean mEnable = true;
|
||||
|
||||
private View mContentView;
|
||||
|
||||
private ViewDragHelper mDragHelper;
|
||||
|
||||
private float mScrollPercent;
|
||||
|
||||
private int mContentLeft;
|
||||
|
||||
private int mContentTop;
|
||||
|
||||
/**
|
||||
* The set of listeners to be sent events through.
|
||||
*/
|
||||
private List<SwipeListener> mListeners;
|
||||
|
||||
private Drawable mShadowLeft;
|
||||
|
||||
private Drawable mShadowRight;
|
||||
|
||||
private Drawable mShadowBottom;
|
||||
|
||||
private float mScrimOpacity;
|
||||
|
||||
private int mScrimColor = DEFAULT_SCRIM_COLOR;
|
||||
|
||||
private boolean mInLayout;
|
||||
|
||||
private Rect mTmpRect = new Rect();
|
||||
|
||||
/**
|
||||
* Edge being dragged
|
||||
*/
|
||||
private int mTrackingEdge;
|
||||
|
||||
public SwipeBackLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SwipeBackLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.SwipeBackLayoutStyle);
|
||||
}
|
||||
|
||||
public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs);
|
||||
mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle,
|
||||
R.style.SwipeBackLayout);
|
||||
|
||||
int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1);
|
||||
if (edgeSize > 0)
|
||||
setEdgeSize(edgeSize);
|
||||
int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)];
|
||||
setEdgeTrackingEnabled(mode);
|
||||
|
||||
int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left,
|
||||
R.drawable.shadow_left);
|
||||
int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right,
|
||||
R.drawable.shadow_right);
|
||||
int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom,
|
||||
R.drawable.shadow_bottom);
|
||||
setShadow(shadowLeft, EDGE_LEFT);
|
||||
setShadow(shadowRight, EDGE_RIGHT);
|
||||
setShadow(shadowBottom, EDGE_BOTTOM);
|
||||
a.recycle();
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
final float minVel = MIN_FLING_VELOCITY * density;
|
||||
mDragHelper.setMinVelocity(minVel);
|
||||
mDragHelper.setMaxVelocity(minVel * 2f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sensitivity of the NavigationLayout.
|
||||
*
|
||||
* @param context The application context.
|
||||
* @param sensitivity value between 0 and 1, the final value for touchSlop =
|
||||
* ViewConfiguration.getScaledTouchSlop * (1 / s);
|
||||
*/
|
||||
public void setSensitivity(Context context, float sensitivity) {
|
||||
mDragHelper.setSensitivity(context, sensitivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up contentView which will be moved by user gesture
|
||||
*
|
||||
* @param view
|
||||
*/
|
||||
private void setContentView(View view) {
|
||||
mContentView = view;
|
||||
}
|
||||
|
||||
public void setEnableGesture(boolean enable) {
|
||||
mEnable = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable edge tracking for the selected edges of the parent view. The
|
||||
* callback's
|
||||
* and
|
||||
* methods will only be invoked for edges for which edge tracking has been
|
||||
* enabled.
|
||||
*
|
||||
* @param edgeFlags Combination of edge flags describing the edges to watch
|
||||
* @see #EDGE_LEFT
|
||||
* @see #EDGE_RIGHT
|
||||
* @see #EDGE_BOTTOM
|
||||
*/
|
||||
public void setEdgeTrackingEnabled(int edgeFlags) {
|
||||
mEdgeFlag = edgeFlags;
|
||||
mDragHelper.setEdgeTrackingEnabled(mEdgeFlag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a color to use for the scrim that obscures primary content while a
|
||||
* drawer is open.
|
||||
*
|
||||
* @param color Color to use in 0xAARRGGBB format.
|
||||
*/
|
||||
public void setScrimColor(int color) {
|
||||
mScrimColor = color;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of an edge. This is the range in pixels along the edges of
|
||||
* this view that will actively detect edge touches or drags if edge
|
||||
* tracking is enabled.
|
||||
*
|
||||
* @param size The size of an edge in pixels
|
||||
*/
|
||||
public void setEdgeSize(int size) {
|
||||
mDragHelper.setEdgeSize(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when a swipe event is sent to this
|
||||
* view.
|
||||
*
|
||||
* @param listener the swipe listener to attach to this view
|
||||
* @deprecated use {@link #addSwipeListener} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSwipeListener(SwipeListener listener) {
|
||||
addSwipeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be invoked when a swipe event is sent to this view.
|
||||
*
|
||||
* @param listener the swipe listener to attach to this view
|
||||
*/
|
||||
public void addSwipeListener(SwipeListener listener) {
|
||||
if (mListeners == null) {
|
||||
mListeners = new ArrayList<SwipeListener>();
|
||||
}
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from the set of listeners
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void removeSwipeListener(SwipeListener listener) {
|
||||
if (mListeners == null) {
|
||||
return;
|
||||
}
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
public static interface SwipeListener {
|
||||
/**
|
||||
* Invoke when state change
|
||||
*
|
||||
* @param state flag to describe scroll state
|
||||
* @param scrollPercent scroll percent of this view
|
||||
* @see #STATE_IDLE
|
||||
* @see #STATE_DRAGGING
|
||||
* @see #STATE_SETTLING
|
||||
*/
|
||||
public void onScrollStateChange(int state, float scrollPercent);
|
||||
|
||||
/**
|
||||
* Invoke when edge touched
|
||||
*
|
||||
* @param edgeFlag edge flag describing the edge being touched
|
||||
* @see #EDGE_LEFT
|
||||
* @see #EDGE_RIGHT
|
||||
* @see #EDGE_BOTTOM
|
||||
*/
|
||||
public void onEdgeTouch(int edgeFlag);
|
||||
|
||||
/**
|
||||
* Invoke when scroll percent over the threshold for the first time
|
||||
*/
|
||||
public void onScrollOverThreshold();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set scroll threshold, we will close the activity, when scrollPercent over
|
||||
* this value
|
||||
*
|
||||
* @param threshold
|
||||
*/
|
||||
public void setScrollThresHold(float threshold) {
|
||||
if (threshold >= 1.0f || threshold <= 0) {
|
||||
throw new IllegalArgumentException("Threshold value should be between 0 and 1.0");
|
||||
}
|
||||
mScrollThreshold = threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a drawable used for edge shadow.
|
||||
*
|
||||
* @param shadow Drawable to use
|
||||
* @see #EDGE_LEFT
|
||||
* @see #EDGE_RIGHT
|
||||
* @see #EDGE_BOTTOM
|
||||
*/
|
||||
public void setShadow(Drawable shadow, int edgeFlag) {
|
||||
if ((edgeFlag & EDGE_LEFT) != 0) {
|
||||
mShadowLeft = shadow;
|
||||
} else if ((edgeFlag & EDGE_RIGHT) != 0) {
|
||||
mShadowRight = shadow;
|
||||
} else if ((edgeFlag & EDGE_BOTTOM) != 0) {
|
||||
mShadowBottom = shadow;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a drawable used for edge shadow.
|
||||
*
|
||||
* @param resId Resource of drawable to use
|
||||
* @see #EDGE_LEFT
|
||||
* @see #EDGE_RIGHT
|
||||
* @see #EDGE_BOTTOM
|
||||
*/
|
||||
public void setShadow(int resId, int edgeFlag) {
|
||||
setShadow(getResources().getDrawable(resId), edgeFlag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll out contentView and finish the activity
|
||||
*/
|
||||
public void scrollToFinishActivity() {
|
||||
final int childWidth = mContentView.getWidth();
|
||||
final int childHeight = mContentView.getHeight();
|
||||
|
||||
int left = 0, top = 0;
|
||||
if ((mEdgeFlag & EDGE_LEFT) != 0) {
|
||||
left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE;
|
||||
mTrackingEdge = EDGE_LEFT;
|
||||
} else if ((mEdgeFlag & EDGE_RIGHT) != 0) {
|
||||
left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE;
|
||||
mTrackingEdge = EDGE_RIGHT;
|
||||
} else if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
|
||||
top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE;
|
||||
mTrackingEdge = EDGE_BOTTOM;
|
||||
}
|
||||
|
||||
mDragHelper.smoothSlideViewTo(mContentView, left, top);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (!mEnable) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mDragHelper.shouldInterceptTouchEvent(event);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// FIXME: handle exception
|
||||
// issues #9
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!mEnable) {
|
||||
return false;
|
||||
}
|
||||
mDragHelper.processTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
mInLayout = true;
|
||||
if (mContentView != null)
|
||||
try {
|
||||
mContentView.layout(mContentLeft, mContentTop,
|
||||
mContentLeft + mContentView.getMeasuredWidth(),
|
||||
mContentTop + mContentView.getMeasuredHeight());
|
||||
}catch (Exception e){
|
||||
}
|
||||
|
||||
mInLayout = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
if (!mInLayout) {
|
||||
super.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
||||
final boolean drawContent = child == mContentView;
|
||||
|
||||
boolean ret = super.drawChild(canvas, child, drawingTime);
|
||||
if (mScrimOpacity > 0 && drawContent
|
||||
&& mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
|
||||
drawShadow(canvas, child);
|
||||
drawScrim(canvas, child);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void drawScrim(Canvas canvas, View child) {
|
||||
final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
|
||||
final int alpha = (int) (baseAlpha * mScrimOpacity);
|
||||
final int color = alpha << 24 | (mScrimColor & 0xffffff);
|
||||
|
||||
if ((mTrackingEdge & EDGE_LEFT) != 0) {
|
||||
canvas.clipRect(0, 0, child.getLeft(), getHeight());
|
||||
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
|
||||
canvas.clipRect(child.getRight(), 0, getRight(), getHeight());
|
||||
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
|
||||
canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight());
|
||||
}
|
||||
canvas.drawColor(color);
|
||||
}
|
||||
|
||||
private void drawShadow(Canvas canvas, View child) {
|
||||
final Rect childRect = mTmpRect;
|
||||
child.getHitRect(childRect);
|
||||
|
||||
if ((mEdgeFlag & EDGE_LEFT) != 0) {
|
||||
mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top,
|
||||
childRect.left, childRect.bottom);
|
||||
mShadowLeft.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
|
||||
mShadowLeft.draw(canvas);
|
||||
}
|
||||
|
||||
if ((mEdgeFlag & EDGE_RIGHT) != 0) {
|
||||
mShadowRight.setBounds(childRect.right, childRect.top,
|
||||
childRect.right + mShadowRight.getIntrinsicWidth(), childRect.bottom);
|
||||
mShadowRight.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
|
||||
mShadowRight.draw(canvas);
|
||||
}
|
||||
|
||||
if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
|
||||
mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right,
|
||||
childRect.bottom + mShadowBottom.getIntrinsicHeight());
|
||||
mShadowBottom.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
|
||||
mShadowBottom.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void attachToActivity(Activity activity) {
|
||||
mActivity = activity;
|
||||
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{
|
||||
android.R.attr.windowBackground
|
||||
});
|
||||
int background = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
|
||||
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
|
||||
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
|
||||
decorChild.setBackgroundResource(background);
|
||||
decor.removeView(decorChild);
|
||||
addView(decorChild);
|
||||
setContentView(decorChild);
|
||||
decor.addView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
mScrimOpacity = 1 - mScrollPercent;
|
||||
if (mDragHelper.continueSettling(true)) {
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewDragCallback extends ViewDragHelper.Callback {
|
||||
private boolean mIsScrollOverValid;
|
||||
|
||||
@Override
|
||||
public boolean tryCaptureView(View view, int i) {
|
||||
boolean ret = mDragHelper.isEdgeTouched(mEdgeFlag, i);
|
||||
if (ret) {
|
||||
if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) {
|
||||
mTrackingEdge = EDGE_LEFT;
|
||||
} else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) {
|
||||
mTrackingEdge = EDGE_RIGHT;
|
||||
} else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) {
|
||||
mTrackingEdge = EDGE_BOTTOM;
|
||||
}
|
||||
if (mListeners != null && !mListeners.isEmpty()) {
|
||||
for (SwipeListener listener : mListeners) {
|
||||
listener.onEdgeTouch(mTrackingEdge);
|
||||
}
|
||||
}
|
||||
mIsScrollOverValid = true;
|
||||
}
|
||||
boolean directionCheck = false;
|
||||
if (mEdgeFlag == EDGE_LEFT || mEdgeFlag == EDGE_RIGHT) {
|
||||
directionCheck = !mDragHelper.checkTouchSlop(ViewDragHelper.DIRECTION_VERTICAL, i);
|
||||
} else if (mEdgeFlag == EDGE_BOTTOM) {
|
||||
directionCheck = !mDragHelper
|
||||
.checkTouchSlop(ViewDragHelper.DIRECTION_HORIZONTAL, i);
|
||||
} else if (mEdgeFlag == EDGE_ALL) {
|
||||
directionCheck = true;
|
||||
}
|
||||
return ret & directionCheck;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewHorizontalDragRange(View child) {
|
||||
return mEdgeFlag & (EDGE_LEFT | EDGE_RIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return mEdgeFlag & EDGE_BOTTOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
super.onViewPositionChanged(changedView, left, top, dx, dy);
|
||||
if ((mTrackingEdge & EDGE_LEFT) != 0) {
|
||||
mScrollPercent = Math.abs((float) left
|
||||
/ (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth()));
|
||||
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
|
||||
mScrollPercent = Math.abs((float) left
|
||||
/ (mContentView.getWidth() + mShadowRight.getIntrinsicWidth()));
|
||||
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
|
||||
mScrollPercent = Math.abs((float) top
|
||||
/ (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight()));
|
||||
}
|
||||
mContentLeft = left;
|
||||
mContentTop = top;
|
||||
invalidate();
|
||||
if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) {
|
||||
mIsScrollOverValid = true;
|
||||
}
|
||||
if (mListeners != null && !mListeners.isEmpty()
|
||||
&& mDragHelper.getViewDragState() == STATE_DRAGGING
|
||||
&& mScrollPercent >= mScrollThreshold && mIsScrollOverValid) {
|
||||
mIsScrollOverValid = false;
|
||||
for (SwipeListener listener : mListeners) {
|
||||
listener.onScrollOverThreshold();
|
||||
}
|
||||
}
|
||||
|
||||
if (mScrollPercent >= 1) {
|
||||
if (!mActivity.isFinishing()) {
|
||||
mActivity.finish();
|
||||
mActivity.overridePendingTransition(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
final int childWidth = releasedChild.getWidth();
|
||||
final int childHeight = releasedChild.getHeight();
|
||||
|
||||
int left = 0, top = 0;
|
||||
if ((mTrackingEdge & EDGE_LEFT) != 0) {
|
||||
left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth
|
||||
+ mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;
|
||||
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
|
||||
left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth
|
||||
+ mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0;
|
||||
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
|
||||
top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight
|
||||
+ mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0;
|
||||
}
|
||||
|
||||
mDragHelper.settleCapturedViewAt(left, top);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionHorizontal(View child, int left, int dx) {
|
||||
int ret = 0;
|
||||
if ((mTrackingEdge & EDGE_LEFT) != 0) {
|
||||
ret = Math.min(child.getWidth(), Math.max(left, 0));
|
||||
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
|
||||
ret = Math.min(0, Math.max(left, -child.getWidth()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||
int ret = 0;
|
||||
if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
|
||||
ret = Math.min(0, Math.max(top, -child.getHeight()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
super.onViewDragStateChanged(state);
|
||||
if (mListeners != null && !mListeners.isEmpty()) {
|
||||
for (SwipeListener listener : mListeners) {
|
||||
listener.onScrollStateChange(state, mScrollPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
package com.chwl.library.threadmgr;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/6/27
|
||||
*/
|
||||
public class SchedulePolicy implements RejectedExecutionHandler {
|
||||
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
Log.e("mouse_debug", ResUtil.getString(R.string.xchat_android_library_threadmgr_schedulepolicy_01));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.chwl.library.threadmgr;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.chwl.library.R;
|
||||
import com.chwl.library.utils.ResUtil;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/5/27
|
||||
*/
|
||||
public class SpeakPolicy implements RejectedExecutionHandler {
|
||||
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
Log.e("mouse_debug", ResUtil.getString(R.string.xchat_android_library_threadmgr_speakpolicy_01));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.chwl.library.threadmgr;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/5/27
|
||||
*/
|
||||
public class SpeakThreadFactory implements ThreadFactory {
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable r) {
|
||||
return new Thread(r, "speak-thread-pool");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package com.chwl.library.threadmgr;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/5/27
|
||||
*/
|
||||
public class ThreadPoolManager {
|
||||
|
||||
/**
|
||||
* 光圈的线程池
|
||||
*/
|
||||
private ThreadPoolExecutor speakExecutor;
|
||||
|
||||
/**
|
||||
* 执行周期任务的线程池
|
||||
*/
|
||||
private ThreadPoolExecutor scheduleExecutor;
|
||||
|
||||
private static final class Helper {
|
||||
public static final ThreadPoolManager INSTANCE = new ThreadPoolManager();
|
||||
}
|
||||
|
||||
private ThreadPoolManager() {
|
||||
|
||||
}
|
||||
|
||||
public void init() {
|
||||
createSpeakExecutor();
|
||||
createSingleExecutor();
|
||||
}
|
||||
|
||||
public static ThreadPoolManager instance() {
|
||||
return Helper.INSTANCE;
|
||||
}
|
||||
|
||||
private void createSpeakExecutor() {
|
||||
//不让回收核心线程
|
||||
speakExecutor = new ThreadPoolExecutor(10, 20,
|
||||
180L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100), new SpeakThreadFactory(), new SpeakPolicy());
|
||||
}
|
||||
|
||||
private void createSingleExecutor() {
|
||||
scheduleExecutor = new ThreadPoolExecutor(1, 5,
|
||||
180L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100), new SchedulePolicy());
|
||||
scheduleExecutor.allowCoreThreadTimeOut(true);
|
||||
}
|
||||
|
||||
public ThreadPoolExecutor getSpeakExecutor() {
|
||||
if (speakExecutor == null) {
|
||||
createSpeakExecutor();
|
||||
}
|
||||
return speakExecutor;
|
||||
}
|
||||
|
||||
public ThreadPoolExecutor getScheduleExecutor() {
|
||||
if (scheduleExecutor == null) {
|
||||
createSingleExecutor();
|
||||
}
|
||||
return scheduleExecutor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.chwl.library.common.Constants;
|
||||
import com.chwl.library.utils.config.BasicConfig;
|
||||
import com.chwl.library.utils.log.MLog;
|
||||
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: crid
|
||||
* Date: 9/3/13
|
||||
* Time: 9:23 AM
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
public class AppMetaDataUtil {
|
||||
|
||||
/**
|
||||
* @param
|
||||
* @return 渠道名称
|
||||
* <p/>
|
||||
* IMPORTANT: 需要在AndroidManifest.xml 配置 <meta-data android:name="Channel_ID" android:value="official"/>
|
||||
*/
|
||||
public static String getChannelID() {
|
||||
String channelID = BasicConfig.INSTANCE.getChannel();
|
||||
channelID = TextUtils.isEmpty(channelID) ? Constants.GOOGLE : channelID;
|
||||
return channelID;
|
||||
}
|
||||
|
||||
public static String getSvnBuildVersion(Context context) {
|
||||
return getMetaString(context, "SvnBuildVersion");
|
||||
}
|
||||
|
||||
public static String getMetaString(Context context, String key) {
|
||||
String value = "";
|
||||
try {
|
||||
if (context != null) {
|
||||
String pkgName = context.getPackageName();
|
||||
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(pkgName, PackageManager.GET_META_DATA);
|
||||
value = appInfo.metaData.getString(key);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
MLog.error("AppMetaDataUtil getSvnBuildVersion", e);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String getUpdateId(Context context) {
|
||||
return getMetaString(context, "UpdateId");
|
||||
}
|
||||
}
|
5513
library/src/main/java/com/chwl/library/utils/ArrayUtils.java
Normal file
5513
library/src/main/java/com/chwl/library/utils/ArrayUtils.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.collection.LruCache;
|
||||
|
||||
/**
|
||||
* @author Rowand jj
|
||||
*
|
||||
*ʹ<><CAB9>Lrucache<68><65><EFBFBD><EFBFBD>bitmap<61>Ĺ<EFBFBD><C4B9><EFBFBD><EFBFBD><EFBFBD>
|
||||
*/
|
||||
public class BitmapLruCacheHelper
|
||||
{
|
||||
private static final String TAG = "BitmapLruCacheHelper";
|
||||
private static BitmapLruCacheHelper instance = new BitmapLruCacheHelper();
|
||||
LruCache<String, Bitmap> cache = null;
|
||||
//<2F><><EFBFBD><EFBFBD>
|
||||
private BitmapLruCacheHelper()
|
||||
{
|
||||
int maxSize = (int) (Runtime.getRuntime().maxMemory() / (float) 8);
|
||||
cache = new LruCache<String, Bitmap>(maxSize)
|
||||
{
|
||||
@Override
|
||||
protected int sizeOf(String key, Bitmap value)
|
||||
{
|
||||
return value.getRowBytes()*value.getHeight();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static BitmapLruCacheHelper getInstance()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
/**
|
||||
*<2A><><EFBFBD>뻺<EFBFBD><EBBBBA>
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public void addBitmapToMemCache(String key, Bitmap value)
|
||||
{
|
||||
if(key == null || value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(cache!=null && getBitmapFromMemCache(key)==null)
|
||||
{
|
||||
cache.put(key, value);
|
||||
Log.i(TAG,"put bitmap to lrucache success");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <20>ӻ<EFBFBD><D3BB><EFBFBD><EFBFBD>л<EFBFBD>ȡͼƬ
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public Bitmap getBitmapFromMemCache(String key)
|
||||
{
|
||||
if(key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Bitmap bitmap = cache.get(key);
|
||||
Log.i(TAG,"get bitmap from lrucache,bitmap="+bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* <20><>ָ<EFBFBD><D6B8>bitmap<61>ӻ<EFBFBD><D3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD>
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public Bitmap removeBitmapFromMemCache(String key)
|
||||
{
|
||||
if(key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return cache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
75
library/src/main/java/com/chwl/library/utils/BlankUtil.java
Normal file
75
library/src/main/java/com/chwl/library/utils/BlankUtil.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 判断对象是否为空
|
||||
* Created on 2010-5-25
|
||||
*/
|
||||
public class BlankUtil {
|
||||
|
||||
/**
|
||||
* 判断字符串是否为空
|
||||
* @return 空:true 否则:false
|
||||
*/
|
||||
public static boolean isBlank(final String str) {
|
||||
return (str == null) || (str.trim().length() <= 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断字符是否为空
|
||||
* @param cha
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlank(final Character cha){
|
||||
return (cha==null) || cha.equals(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否为空
|
||||
*/
|
||||
public static boolean isBlank(final Object obj) {
|
||||
return (obj==null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数组是否为空
|
||||
* @param objs
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlank(final Object[] objs) {
|
||||
return (objs == null) || (objs.length <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Collectionj是否为空
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlank(final Collection<?> obj) {
|
||||
return (obj == null) || (obj.size() <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Set是否为空
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlank(final Set<?> obj) {
|
||||
return (obj == null) || (obj.size() <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Map是否为空
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
public static boolean isBlank(final Map<Object, Object> obj) {
|
||||
return (obj == null) || (obj.size() <= 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.Gravity;
|
||||
import android.widget.TextView;
|
||||
|
||||
public final class CenterDrawableHelper {
|
||||
static final int DRAWABLE_LEFT = 0;
|
||||
static final int DRAWABLE_TOP = 1;
|
||||
static final int DRAWABLE_RIGHT = 2;
|
||||
static final int DRAWABLE_BOTTOM = 3;
|
||||
|
||||
private static void onCenterDraw(TextView view, Canvas canvas, Drawable drawable, int gravity) {
|
||||
if (drawable == null) return;
|
||||
int drawablePadding = view.getCompoundDrawablePadding();
|
||||
int ratio = 1;
|
||||
float total;
|
||||
switch (gravity) {
|
||||
case Gravity.RIGHT:
|
||||
ratio = -1;
|
||||
case Gravity.LEFT:
|
||||
total = view.getPaint().measureText(view.getText().toString()) + drawable.getIntrinsicWidth()
|
||||
+ drawablePadding + view.getPaddingLeft() + view.getPaddingRight();
|
||||
canvas.translate(ratio * (view.getWidth() - total) / 2, 0);
|
||||
break;
|
||||
case Gravity.BOTTOM:
|
||||
ratio = -1;
|
||||
case Gravity.TOP:
|
||||
Paint.FontMetrics fontMetrics0 = view.getPaint().getFontMetrics();
|
||||
total = fontMetrics0.descent - fontMetrics0.ascent + drawable.getIntrinsicHeight()
|
||||
+ drawablePadding + view.getPaddingTop() + view.getPaddingBottom();
|
||||
canvas.translate(0, ratio * (view.getHeight() - total) / 2);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
public static void preDraw(TextView view, Canvas canvas) {
|
||||
Drawable[] drawables = view.getCompoundDrawables();
|
||||
if (drawables.length > 0) {
|
||||
if (drawables[DRAWABLE_LEFT] != null) {
|
||||
view.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
|
||||
onCenterDraw(view, canvas, drawables[DRAWABLE_LEFT], Gravity.LEFT);
|
||||
} else if (drawables[DRAWABLE_TOP] != null) {
|
||||
view.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
|
||||
onCenterDraw(view, canvas, drawables[DRAWABLE_TOP], Gravity.TOP);
|
||||
} else if (drawables[DRAWABLE_RIGHT] != null) {
|
||||
view.setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
|
||||
onCenterDraw(view, canvas, drawables[DRAWABLE_RIGHT], Gravity.RIGHT);
|
||||
} else if (drawables[DRAWABLE_BOTTOM] != null) {
|
||||
view.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
|
||||
onCenterDraw(view, canvas, drawables[DRAWABLE_BOTTOM], Gravity.BOTTOM);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.chwl.library.utils;
|
||||
|
||||
/**
|
||||
* <p>Operations on {@link CharSequence} that are
|
||||
* {@code null} safe.</p>
|
||||
*
|
||||
* @version $Id: CharSequenceUtils.java 1469220 2013-04-18 08:15:47Z bayard $
|
||||
* @see CharSequence
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CharSequenceUtils {
|
||||
|
||||
/**
|
||||
* <p>{@code CharSequenceUtils} instances should NOT be constructed in
|
||||
* standard programming. </p>
|
||||
* <p/>
|
||||
* <p>This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.</p>
|
||||
*/
|
||||
public CharSequenceUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Returns a new {@code CharSequence} that is a subsequence of this
|
||||
* sequence starting with the {@code char} value at the specified index.</p>
|
||||
* <p/>
|
||||
* <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
|
||||
* The length (in {@code char}) of the returned sequence is {@code length() - start},
|
||||
* so if {@code start == end} then an empty sequence is returned.</p>
|
||||
*
|
||||
* @param cs the specified subsequence, null returns null
|
||||
* @param start the start index, inclusive, valid
|
||||
* @return a new subsequence, may be null
|
||||
* @throws IndexOutOfBoundsException if {@code start} is negative or if
|
||||
* {@code start} is greater than {@code length()}
|
||||
*/
|
||||
public static CharSequence subSequence(final CharSequence cs, final int start) {
|
||||
return cs == null ? null : cs.subSequence(start, cs.length());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Finds the first index in the {@code CharSequence} that matches the
|
||||
* specified character.</p>
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed, not null
|
||||
* @param searchChar the char to be searched for
|
||||
* @param start the start index, negative starts at the string start
|
||||
* @return the index where the search char was found, -1 if not found
|
||||
*/
|
||||
static int indexOf(final CharSequence cs, final int searchChar, int start) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).indexOf(searchChar, start);
|
||||
} else {
|
||||
final int sz = cs.length();
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
for (int i = start; i < sz; i++) {
|
||||
if (cs.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the {@code CharSequence} to be searched for
|
||||
* @param start the start index
|
||||
* @return the index where the search sequence was found
|
||||
*/
|
||||
static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
|
||||
return cs.toString().indexOf(searchChar.toString(), start);
|
||||
// if (cs instanceof String && searchChar instanceof String) {
|
||||
// // TODO: Do we assume searchChar is usually relatively small;
|
||||
// // If so then calling toString() on it is better than reverting to
|
||||
// // the green implementation in the else block
|
||||
// return ((String) cs).indexOf((String) searchChar, start);
|
||||
// } else {
|
||||
// // TODO: Implement rather than convert to String
|
||||
// return cs.toString().indexOf(searchChar.toString(), start);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds the last index in the {@code CharSequence} that matches the
|
||||
* specified character.</p>
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the char to be searched for
|
||||
* @param start the start index, negative returns -1, beyond length starts at end
|
||||
* @return the index where the search char was found, -1 if not found
|
||||
*/
|
||||
static int lastIndexOf(final CharSequence cs, final int searchChar, int start) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).lastIndexOf(searchChar, start);
|
||||
} else {
|
||||
final int sz = cs.length();
|
||||
if (start < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (start >= sz) {
|
||||
start = sz - 1;
|
||||
}
|
||||
for (int i = start; i >= 0; --i) {
|
||||
if (cs.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param searchChar the {@code CharSequence} to be searched for
|
||||
* @param start the start index
|
||||
* @return the index where the search sequence was found
|
||||
*/
|
||||
static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
|
||||
return cs.toString().lastIndexOf(searchChar.toString(), start);
|
||||
// if (cs instanceof String && searchChar instanceof String) {
|
||||
// // TODO: Do we assume searchChar is usually relatively small;
|
||||
// // If so then calling toString() on it is better than reverting to
|
||||
// // the green implementation in the else block
|
||||
// return ((String) cs).lastIndexOf((String) searchChar, start);
|
||||
// } else {
|
||||
// // TODO: Implement rather than convert to String
|
||||
// return cs.toString().lastIndexOf(searchChar.toString(), start);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Green implementation of toCharArray.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @return the resulting char array
|
||||
*/
|
||||
static char[] toCharArray(final CharSequence cs) {
|
||||
if (cs instanceof String) {
|
||||
return ((String) cs).toCharArray();
|
||||
} else {
|
||||
final int sz = cs.length();
|
||||
final char[] array = new char[cs.length()];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
array[i] = cs.charAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Green implementation of regionMatches.
|
||||
*
|
||||
* @param cs the {@code CharSequence} to be processed
|
||||
* @param ignoreCase whether or not to be case insensitive
|
||||
* @param thisStart the index to start on the {@code cs} CharSequence
|
||||
* @param substring the {@code CharSequence} to be looked for
|
||||
* @param start the index to start on the {@code substring} CharSequence
|
||||
* @param length character length of the region
|
||||
* @return whether the region matched
|
||||
*/
|
||||
static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
|
||||
final CharSequence substring, final int start, final int length) {
|
||||
if (cs instanceof String && substring instanceof String) {
|
||||
return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
|
||||
} else {
|
||||
int index1 = thisStart;
|
||||
int index2 = start;
|
||||
int tmpLen = length;
|
||||
|
||||
while (tmpLen-- > 0) {
|
||||
char c1 = cs.charAt(index1++);
|
||||
char c2 = substring.charAt(index2++);
|
||||
|
||||
if (c1 == c2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ignoreCase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The same check as in String.regionMatches():
|
||||
if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
|
||||
&& Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
539
library/src/main/java/com/chwl/library/utils/CharUtils.java
Normal file
539
library/src/main/java/com/chwl/library/utils/CharUtils.java
Normal file
@@ -0,0 +1,539 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.chwl.library.utils;
|
||||
|
||||
/**
|
||||
* <p>Operations on char primitives and Character objects.</p>
|
||||
*
|
||||
* <p>This class tries to handle {@code null} input gracefully.
|
||||
* An exception will not be thrown for a {@code null} input.
|
||||
* Each method documents its behaviour in more detail.</p>
|
||||
*
|
||||
* <p>#ThreadSafe#</p>
|
||||
* @since 2.1
|
||||
* @version $Id: CharUtils.java 1531257 2013-10-11 11:25:28Z britter $
|
||||
*/
|
||||
public class CharUtils {
|
||||
|
||||
private static final String[] CHAR_STRING_ARRAY = new String[128];
|
||||
|
||||
/**
|
||||
* {@code \u000a} linefeed LF ('\n').
|
||||
*
|
||||
* @see <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
|
||||
* for Character and String Literals</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static final char LF = '\n';
|
||||
|
||||
/**
|
||||
* {@code \u000d} carriage return CR ('\r').
|
||||
*
|
||||
* @see <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">JLF: Escape Sequences
|
||||
* for Character and String Literals</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static final char CR = '\r';
|
||||
|
||||
|
||||
static {
|
||||
for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) {
|
||||
CHAR_STRING_ARRAY[c] = String.valueOf(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>{@code CharUtils} instances should NOT be constructed in standard programming.
|
||||
* Instead, the class should be used as {@code CharUtils.toString('c');}.</p>
|
||||
*
|
||||
* <p>This constructor is public to permit tools that require a JavaBean instance
|
||||
* to operate.</p>
|
||||
*/
|
||||
public CharUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to a Character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same Character object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toCharacterObject(' ') = ' '
|
||||
* CharUtils.toCharacterObject('A') = 'A'
|
||||
* </pre>
|
||||
*
|
||||
* @deprecated Java 5 introduced {@link Character#valueOf(char)} which caches chars 0 through 127.
|
||||
* @param ch the character to convert
|
||||
* @return a Character of the specified character
|
||||
*/
|
||||
@Deprecated
|
||||
public static Character toCharacterObject(final char ch) {
|
||||
return Character.valueOf(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the String to a Character using the first character, returning
|
||||
* null for empty Strings.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same Character object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toCharacterObject(null) = null
|
||||
* CharUtils.toCharacterObject("") = null
|
||||
* CharUtils.toCharacterObject("A") = 'A'
|
||||
* CharUtils.toCharacterObject("BA") = 'B'
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @return the Character value of the first letter of the String
|
||||
*/
|
||||
public static Character toCharacterObject(final String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return null;
|
||||
}
|
||||
return Character.valueOf(str.charAt(0));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the Character to a char throwing an exception for {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(' ') = ' '
|
||||
* CharUtils.toChar('A') = 'A'
|
||||
* CharUtils.toChar(null) throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the char value of the Character
|
||||
* @throws IllegalArgumentException if the Character is null
|
||||
*/
|
||||
public static char toChar(final Character ch) {
|
||||
if (ch == null) {
|
||||
throw new IllegalArgumentException("The Character must not be null");
|
||||
}
|
||||
return ch.charValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the Character to a char handling {@code null}.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(null, 'X') = 'X'
|
||||
* CharUtils.toChar(' ', 'X') = ' '
|
||||
* CharUtils.toChar('A', 'X') = 'A'
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the value to use if the Character is null
|
||||
* @return the char value of the Character or the default if null
|
||||
*/
|
||||
public static char toChar(final Character ch, final char defaultValue) {
|
||||
if (ch == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ch.charValue();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the String to a char using the first character, throwing
|
||||
* an exception on empty Strings.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar("A") = 'A'
|
||||
* CharUtils.toChar("BA") = 'B'
|
||||
* CharUtils.toChar(null) throws IllegalArgumentException
|
||||
* CharUtils.toChar("") throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @return the char value of the first letter of the String
|
||||
* @throws IllegalArgumentException if the String is empty
|
||||
*/
|
||||
public static char toChar(final String str) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
throw new IllegalArgumentException("The String must not be empty");
|
||||
}
|
||||
return str.charAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the String to a char using the first character, defaulting
|
||||
* the value on empty Strings.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toChar(null, 'X') = 'X'
|
||||
* CharUtils.toChar("", 'X') = 'X'
|
||||
* CharUtils.toChar("A", 'X') = 'A'
|
||||
* CharUtils.toChar("BA", 'X') = 'B'
|
||||
* </pre>
|
||||
*
|
||||
* @param str the character to convert
|
||||
* @param defaultValue the value to use if the Character is null
|
||||
* @return the char value of the first letter of the String or the default if null
|
||||
*/
|
||||
public static char toChar(final String str, final char defaultValue) {
|
||||
if (StringUtils.isEmpty(str)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return str.charAt(0);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3') = 3
|
||||
* CharUtils.toIntValue('A') throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the int value of the character
|
||||
* @throws IllegalArgumentException if the character is not ASCII numeric
|
||||
*/
|
||||
public static int toIntValue(final char ch) {
|
||||
if (isAsciiNumeric(ch) == false) {
|
||||
throw new IllegalArgumentException("The character " + ch + " is not in the range '0' - '9'");
|
||||
}
|
||||
return ch - 48;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3', -1) = 3
|
||||
* CharUtils.toIntValue('A', -1) = -1
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the default value to use if the character is not numeric
|
||||
* @return the int value of the character
|
||||
*/
|
||||
public static int toIntValue(final char ch, final int defaultValue) {
|
||||
if (isAsciiNumeric(ch) == false) {
|
||||
return defaultValue;
|
||||
}
|
||||
return ch - 48;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue('3') = 3
|
||||
* CharUtils.toIntValue(null) throws IllegalArgumentException
|
||||
* CharUtils.toIntValue('A') throws IllegalArgumentException
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert, not null
|
||||
* @return the int value of the character
|
||||
* @throws IllegalArgumentException if the Character is not ASCII numeric or is null
|
||||
*/
|
||||
public static int toIntValue(final Character ch) {
|
||||
if (ch == null) {
|
||||
throw new IllegalArgumentException("The character must not be null");
|
||||
}
|
||||
return toIntValue(ch.charValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to the Integer it represents, throwing an
|
||||
* exception if the character is not numeric.</p>
|
||||
*
|
||||
* <p>This method coverts the char '1' to the int 1 and so on.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toIntValue(null, -1) = -1
|
||||
* CharUtils.toIntValue('3', -1) = 3
|
||||
* CharUtils.toIntValue('A', -1) = -1
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @param defaultValue the default value to use if the character is not numeric
|
||||
* @return the int value of the character
|
||||
*/
|
||||
public static int toIntValue(final Character ch, final int defaultValue) {
|
||||
if (ch == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return toIntValue(ch.charValue(), defaultValue);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the character to a String that contains the one character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same String object each time.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toString(' ') = " "
|
||||
* CharUtils.toString('A') = "A"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return a String containing the one specified character
|
||||
*/
|
||||
public static String toString(final char ch) {
|
||||
if (ch < 128) {
|
||||
return CHAR_STRING_ARRAY[ch];
|
||||
}
|
||||
return new String(new char[] {ch});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the character to a String that contains the one character.</p>
|
||||
*
|
||||
* <p>For ASCII 7 bit characters, this uses a cache that will return the
|
||||
* same String object each time.</p>
|
||||
*
|
||||
* <p>If {@code null} is passed in, {@code null} will be returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.toString(null) = null
|
||||
* CharUtils.toString(' ') = " "
|
||||
* CharUtils.toString('A') = "A"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return a String containing the one specified character
|
||||
*/
|
||||
public static String toString(final Character ch) {
|
||||
if (ch == null) {
|
||||
return null;
|
||||
}
|
||||
return toString(ch.charValue());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Converts the string to the Unicode format '\u0020'.</p>
|
||||
*
|
||||
* <p>This format is the Java source code format.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.unicodeEscaped(' ') = "\u0020"
|
||||
* CharUtils.unicodeEscaped('A') = "\u0041"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert
|
||||
* @return the escaped Unicode string
|
||||
*/
|
||||
public static String unicodeEscaped(final char ch) {
|
||||
if (ch < 0x10) {
|
||||
return "\\u000" + Integer.toHexString(ch);
|
||||
} else if (ch < 0x100) {
|
||||
return "\\u00" + Integer.toHexString(ch);
|
||||
} else if (ch < 0x1000) {
|
||||
return "\\u0" + Integer.toHexString(ch);
|
||||
}
|
||||
return "\\u" + Integer.toHexString(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts the string to the Unicode format '\u0020'.</p>
|
||||
*
|
||||
* <p>This format is the Java source code format.</p>
|
||||
*
|
||||
* <p>If {@code null} is passed in, {@code null} will be returned.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.unicodeEscaped(null) = null
|
||||
* CharUtils.unicodeEscaped(' ') = "\u0020"
|
||||
* CharUtils.unicodeEscaped('A') = "\u0041"
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to convert, may be null
|
||||
* @return the escaped Unicode string, null if null input
|
||||
*/
|
||||
public static String unicodeEscaped(final Character ch) {
|
||||
if (ch == null) {
|
||||
return null;
|
||||
}
|
||||
return unicodeEscaped(ch.charValue());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAscii('a') = true
|
||||
* CharUtils.isAscii('A') = true
|
||||
* CharUtils.isAscii('3') = true
|
||||
* CharUtils.isAscii('-') = true
|
||||
* CharUtils.isAscii('\n') = true
|
||||
* CharUtils.isAscii('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if less than 128
|
||||
*/
|
||||
public static boolean isAscii(final char ch) {
|
||||
return ch < 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit printable.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiPrintable('a') = true
|
||||
* CharUtils.isAsciiPrintable('A') = true
|
||||
* CharUtils.isAsciiPrintable('3') = true
|
||||
* CharUtils.isAsciiPrintable('-') = true
|
||||
* CharUtils.isAsciiPrintable('\n') = false
|
||||
* CharUtils.isAsciiPrintable('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 32 and 126 inclusive
|
||||
*/
|
||||
public static boolean isAsciiPrintable(final char ch) {
|
||||
return ch >= 32 && ch < 127;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit control.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiControl('a') = false
|
||||
* CharUtils.isAsciiControl('A') = false
|
||||
* CharUtils.isAsciiControl('3') = false
|
||||
* CharUtils.isAsciiControl('-') = false
|
||||
* CharUtils.isAsciiControl('\n') = true
|
||||
* CharUtils.isAsciiControl('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if less than 32 or equals 127
|
||||
*/
|
||||
public static boolean isAsciiControl(final char ch) {
|
||||
return ch < 32 || ch == 127;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlpha('a') = true
|
||||
* CharUtils.isAsciiAlpha('A') = true
|
||||
* CharUtils.isAsciiAlpha('3') = false
|
||||
* CharUtils.isAsciiAlpha('-') = false
|
||||
* CharUtils.isAsciiAlpha('\n') = false
|
||||
* CharUtils.isAsciiAlpha('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 65 and 90 or 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlpha(final char ch) {
|
||||
return isAsciiAlphaUpper(ch) || isAsciiAlphaLower(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic upper case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphaUpper('a') = false
|
||||
* CharUtils.isAsciiAlphaUpper('A') = true
|
||||
* CharUtils.isAsciiAlphaUpper('3') = false
|
||||
* CharUtils.isAsciiAlphaUpper('-') = false
|
||||
* CharUtils.isAsciiAlphaUpper('\n') = false
|
||||
* CharUtils.isAsciiAlphaUpper('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 65 and 90 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphaUpper(final char ch) {
|
||||
return ch >= 'A' && ch <= 'Z';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit alphabetic lower case.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphaLower('a') = true
|
||||
* CharUtils.isAsciiAlphaLower('A') = false
|
||||
* CharUtils.isAsciiAlphaLower('3') = false
|
||||
* CharUtils.isAsciiAlphaLower('-') = false
|
||||
* CharUtils.isAsciiAlphaLower('\n') = false
|
||||
* CharUtils.isAsciiAlphaLower('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphaLower(final char ch) {
|
||||
return ch >= 'a' && ch <= 'z';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit numeric.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiNumeric('a') = false
|
||||
* CharUtils.isAsciiNumeric('A') = false
|
||||
* CharUtils.isAsciiNumeric('3') = true
|
||||
* CharUtils.isAsciiNumeric('-') = false
|
||||
* CharUtils.isAsciiNumeric('\n') = false
|
||||
* CharUtils.isAsciiNumeric('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 48 and 57 inclusive
|
||||
*/
|
||||
public static boolean isAsciiNumeric(final char ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the character is ASCII 7 bit numeric.</p>
|
||||
*
|
||||
* <pre>
|
||||
* CharUtils.isAsciiAlphanumeric('a') = true
|
||||
* CharUtils.isAsciiAlphanumeric('A') = true
|
||||
* CharUtils.isAsciiAlphanumeric('3') = true
|
||||
* CharUtils.isAsciiAlphanumeric('-') = false
|
||||
* CharUtils.isAsciiAlphanumeric('\n') = false
|
||||
* CharUtils.isAsciiAlphanumeric('©') = false
|
||||
* </pre>
|
||||
*
|
||||
* @param ch the character to check
|
||||
* @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
|
||||
*/
|
||||
public static boolean isAsciiAlphanumeric(final char ch) {
|
||||
return isAsciiAlpha(ch) || isAsciiNumeric(ch);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
public class CommonUtils {
|
||||
private static long lastClickTime;
|
||||
public static boolean isFastDoubleClick(long timeLong) {
|
||||
long time = System.currentTimeMillis();
|
||||
long timeD = time - lastClickTime;
|
||||
if ( 0 < timeD && timeD < timeLong) {
|
||||
return true;
|
||||
}
|
||||
lastClickTime = time;
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.orhanobut.logger.Logger;
|
||||
|
||||
/**
|
||||
* 设备信息工具类
|
||||
*/
|
||||
public class DeviceUtils {
|
||||
private static final String TAG = "DeviceUtils";
|
||||
|
||||
/**
|
||||
* 检查指定包名的APP是否已安装了
|
||||
*
|
||||
* @param context 上下文对象
|
||||
* @param packageName 包名
|
||||
* @return 是否有安装指定包名的APP,true代表已安装,false代表未安装
|
||||
*/
|
||||
public static boolean isAppInstalled(Context context, String packageName) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
} else {
|
||||
boolean installed = false;
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
|
||||
if (info != null) {
|
||||
installed = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.i("isAppInstalled packageName=" + packageName + " "+ e);
|
||||
}
|
||||
return installed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,71 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.provider.Settings;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by chenran on 2017/10/21.
|
||||
*/
|
||||
|
||||
public class DeviceUuidFactory {
|
||||
private static final String PREFS_FILE = "device_id.xml";
|
||||
private static final String PREFS_DEVICE_ID = "device_id";
|
||||
|
||||
public static String getDeviceId(Context context) {
|
||||
UUID uuid;
|
||||
synchronized (DeviceUuidFactory.class) {
|
||||
final SharedPreferences prefs = context.getSharedPreferences(PREFS_FILE, 0);
|
||||
final String id = prefs.getString(PREFS_DEVICE_ID, null);
|
||||
if (id != null) {
|
||||
// Use the ids previously computed and stored in the prefs file
|
||||
uuid = UUID.fromString(id);
|
||||
} else {
|
||||
final String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
// Use the Android ID unless it's broken, in which case fallback on deviceId,
|
||||
// unless it's not available, then fallback on a random number which we store
|
||||
// to a prefs file
|
||||
if (!"9774d56d682e549c".equals(androidId)) {
|
||||
uuid = UUID.nameUUIDFromBytes(androidId.getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
uuid = UUID.randomUUID();
|
||||
}
|
||||
// Write the value out to the prefs file
|
||||
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString()).apply();
|
||||
}
|
||||
}
|
||||
if (uuid == null) {
|
||||
return "";
|
||||
} else {
|
||||
return uuid.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely"
|
||||
* to be unique across all Android devices. Much more so than ANDROID_ID is.
|
||||
*
|
||||
* The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on
|
||||
* TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back
|
||||
* on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a
|
||||
* usable value.
|
||||
*
|
||||
* In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID
|
||||
* may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2
|
||||
* to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on
|
||||
* a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation.
|
||||
*
|
||||
* Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT
|
||||
* change after a factory reset. Something to be aware of.
|
||||
*
|
||||
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly.
|
||||
*
|
||||
* @see http://code.google.com/p/android/issues/detail?id=10603
|
||||
*
|
||||
* @return a UUID that may be used to uniquely identify your device for most purposes.
|
||||
*/
|
||||
|
||||
}
|
909
library/src/main/java/com/chwl/library/utils/FP.java
Normal file
909
library/src/main/java/com/chwl/library/utils/FP.java
Normal file
@@ -0,0 +1,909 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* A functional programming style utility
|
||||
*/
|
||||
public class FP {
|
||||
|
||||
/*
|
||||
* Generic function object
|
||||
*/
|
||||
|
||||
public static <A, B, C> Tuple<A, B, C> makeTuple(A a, B b, C c) {
|
||||
return new Tuple<A, B, C>(a, b, c);
|
||||
}
|
||||
|
||||
public static <E> Pred<E> negate(final Pred<E> p) {
|
||||
return new Pred<E>() {
|
||||
@Override
|
||||
public boolean pred(E x) {
|
||||
return !p.pred(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static int limit(int x, int low, int high) {
|
||||
return Math.min(Math.max(low, x), high);
|
||||
}
|
||||
|
||||
public static int maximum(int... xs) {
|
||||
int m = Integer.MIN_VALUE;
|
||||
for (int x : xs)
|
||||
m = Math.max(m, x);
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find among a list by using predicate function p
|
||||
*/
|
||||
public static <E> E find(Pred<E> p, List<E> xs) {
|
||||
if (!empty(xs))
|
||||
for (E x : xs)
|
||||
if (p.pred(x))
|
||||
return x;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find among a list for an element
|
||||
*/
|
||||
public static <E> E find(final E x, List<E> xs) {
|
||||
return find(new Pred<E>() {
|
||||
@Override
|
||||
public boolean pred(E y) {
|
||||
return y.equals(x);
|
||||
}
|
||||
}, xs);
|
||||
}
|
||||
|
||||
public static <E> int findIndex(Pred<E> p, List<E> xs) {
|
||||
int i, n = length(xs);
|
||||
for (i = 0; i < n && !p.pred(xs.get(i)); ++i) {
|
||||
// empty
|
||||
}
|
||||
return i == n ? -1 : i;
|
||||
}
|
||||
|
||||
/**
|
||||
* looks up a key in an association list (which is realized by pair)
|
||||
*/
|
||||
public static <K, V> V lookup(K k, List<Pair<K, V>> xs) {
|
||||
if (!empty(xs)) {
|
||||
for (Pair<K, V> x : xs) {
|
||||
if (k == x.first) {
|
||||
return x.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <E> E lookup(int k, SparseArray<E> xs) {
|
||||
return FP.empty(xs) ? null : xs.get(k);
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions over abstract list
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove duplicated items by using a compare function, O(N^2) time bound
|
||||
*/
|
||||
public static <E> List<E> nubBy(final Eq<E> cmp, List<E> xs) {
|
||||
List<E> ys = new ArrayList<E>();
|
||||
if (!empty(xs)) {
|
||||
for (final E x : xs) {
|
||||
if (find(new Pred<E>() {
|
||||
@Override
|
||||
public boolean pred(E y) {
|
||||
return cmp.eq(x, y);
|
||||
}
|
||||
}, ys) == null) {
|
||||
ys.add(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicate items by using Object.equals(). O(N^2) time bound
|
||||
* The name nub means `essence'.
|
||||
*/
|
||||
public static <E> List<E> nub(List<E> xs) {
|
||||
return nubBy(new Eq<E>() {
|
||||
@Override
|
||||
public boolean eq(E x, E y) {
|
||||
return y.equals(x);
|
||||
}
|
||||
}, xs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a collection is either NIL or empty
|
||||
*/
|
||||
public static boolean empty(Collection<?> xs) {
|
||||
return xs == null || xs.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if an array is either NIL or empty
|
||||
*/
|
||||
public static <T> boolean empty(T[] xs) {
|
||||
return xs == null || xs.length == 0;
|
||||
}
|
||||
|
||||
public static boolean empty(SparseArray<?> xs) {
|
||||
return xs == null || xs.size() == 0;
|
||||
}
|
||||
|
||||
public static boolean empty(SparseIntArray xs) {
|
||||
return xs == null || xs.size() == 0;
|
||||
}
|
||||
|
||||
public static boolean empty(int[] xs) {
|
||||
return xs == null || xs.length == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary functions
|
||||
*/
|
||||
|
||||
public static boolean empty(long[] xs) {
|
||||
return xs == null || xs.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a abstract string is either NIL or empty
|
||||
*/
|
||||
public static boolean empty(CharSequence s) {
|
||||
return s == null || s.length() == 0;
|
||||
}
|
||||
|
||||
public static boolean empty(Map<?, ?> m) {
|
||||
return m == null || m.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe return the size of a collection even it's NIL
|
||||
*/
|
||||
public static int size(Collection<?> xs) {
|
||||
return xs == null ? 0 : xs.size();
|
||||
}
|
||||
|
||||
public static int size(CharSequence s) {
|
||||
return s == null ? 0 : s.length();
|
||||
}
|
||||
|
||||
public static <T> int size(T[] xs) {
|
||||
return xs == null ? 0 : xs.length;
|
||||
}
|
||||
|
||||
public static int size(int[] xs) {
|
||||
return xs == null ? 0 : xs.length;
|
||||
}
|
||||
|
||||
public static int size(long[] xs) {
|
||||
return xs == null ? 0 : xs.length;
|
||||
}
|
||||
|
||||
public static int size(Map<?, ?> m) {
|
||||
return m == null ? 0 : m.size();
|
||||
}
|
||||
|
||||
public static int size(SparseArray<?> xs) {
|
||||
return xs == null ? 0 : xs.size();
|
||||
}
|
||||
|
||||
public static int size(SparseIntArray xs) {
|
||||
return xs == null ? 0 : xs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined just as alias of 'size'.
|
||||
*/
|
||||
public static int length(Collection<?> xs) {
|
||||
return size(xs);
|
||||
}
|
||||
|
||||
public static int length(CharSequence s) {
|
||||
return size(s);
|
||||
}
|
||||
|
||||
public static <T> int length(T[] xs) {
|
||||
return size(xs);
|
||||
}
|
||||
|
||||
public static int length(int[] xs) {
|
||||
return size(xs);
|
||||
}
|
||||
|
||||
public static int length(Map<?, ?> m) {
|
||||
return size(m);
|
||||
}
|
||||
|
||||
public static int length(SparseArray<?> xs) {
|
||||
return size(xs);
|
||||
}
|
||||
|
||||
public static int length(SparseIntArray xs) {
|
||||
return size(xs);
|
||||
}
|
||||
|
||||
public static <T> boolean elem(T x, T[] xs) {
|
||||
return !empty(xs) && Arrays.asList(xs).contains(x);
|
||||
}
|
||||
|
||||
public static <T> boolean elem(T x, Collection<T> xs) {
|
||||
return !empty(xs) && xs.contains(x);
|
||||
}
|
||||
|
||||
public static <T> void swap(List<T> xs, int i, int j) {
|
||||
T tmp = xs.get(i);
|
||||
xs.set(i, xs.get(j));
|
||||
xs.set(j, tmp);
|
||||
}
|
||||
|
||||
public static <T> void swap(T[] xs, int i, int j) {
|
||||
T tmp = xs[i];
|
||||
xs[i] = xs[j];
|
||||
xs[j] = tmp;
|
||||
}
|
||||
|
||||
public static <T> void shift(List<T> xs, int from, int to) {
|
||||
T tmp = xs.get(from);
|
||||
for (int d = from < to ? 1 : -1; from != to; from += d) {
|
||||
xs.set(from, xs.get(from + d));
|
||||
}
|
||||
xs.set(to, tmp);
|
||||
}
|
||||
|
||||
public static <T> void shift(T[] xs, int from, int to) {
|
||||
T tmp = xs[from];
|
||||
for (int d = from < to ? 1 : -1; from != to; from += d) {
|
||||
xs[from] = xs[from + d];
|
||||
}
|
||||
xs[to] = tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe add element to a list even the list is empty
|
||||
*/
|
||||
public static <E> List<E> add(List<E> xs, E x) {
|
||||
if (xs == null) {
|
||||
xs = new ArrayList<E>();
|
||||
}
|
||||
xs.add(x);
|
||||
return xs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe remove the first occurrent of x in xs
|
||||
*/
|
||||
public static <E> List<E> delBy(final Eq<E> cmp, List<E> xs, E x) {
|
||||
int i, n = FP.length(xs);
|
||||
for (i = 0; i < n && !cmp.eq(xs.get(i), x); ++i) {
|
||||
// empty
|
||||
}
|
||||
if (i < n) {
|
||||
xs.remove(i);
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
public static <E> List<E> del(List<E> xs, E x) {
|
||||
return delBy(new Eq<E>() {
|
||||
@Override
|
||||
public boolean eq(E x, E y) {
|
||||
return FP.eq(x, y);
|
||||
}
|
||||
}, xs, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* span, applied to a predicate p and a list xs, returns a tuple where first
|
||||
* element is longest prefix (possibly empty) of xs of elements that satisfy
|
||||
* p and second element is the remainder of the list.
|
||||
*/
|
||||
public static <E> Pair<List<E>, List<E>> span(Pred<E> p, List<E> xs) {
|
||||
return Pair.create(takeWhile(p, xs), dropWhile(p, xs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe take the first n elements from a list
|
||||
*/
|
||||
public static <E> List<E> take(int n, List<E> xs) {
|
||||
List<E> ys = new ArrayList<E>();
|
||||
if (empty(xs) || n <= 0) {
|
||||
return ys;
|
||||
}
|
||||
ys.addAll(xs.subList(0, Math.min(n, length(xs))));
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static String take(int n, String s) {
|
||||
return s.substring(0, limit(n, 0, FP.length(s)));
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> take(int n, Map<K, V> xs) {
|
||||
Map<K, V> ys = new HashMap<K, V>();
|
||||
for (Entry<K, V> k : xs.entrySet()) {
|
||||
if (n-- > 0) {
|
||||
ys.put(k.getKey(), k.getValue());
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static <E> List<E> takeWhile(Pred<E> p, List<E> xs) {
|
||||
int i, n = length(xs);
|
||||
for (i = 0; i < n && p.pred(xs.get(i)); ++i) {
|
||||
// empty
|
||||
}
|
||||
return take(i, xs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe drop the first n elements from a list
|
||||
*/
|
||||
public static <E> List<E> drop(int n, List<E> xs) {
|
||||
List<E> ys = new ArrayList<E>();
|
||||
if (xs == null || n > length(xs)) {
|
||||
return ys;
|
||||
}
|
||||
ys.addAll(xs.subList(Math.max(0, n), length(xs)));
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static String drop(int n, String s) {
|
||||
if (s == null || n > length(s)) {
|
||||
return "";
|
||||
}
|
||||
return s.substring(Math.max(0, n));
|
||||
}
|
||||
|
||||
public static <E> List<E> dropWhile(Pred<E> p, List<E> xs) {
|
||||
int i, n = length(xs);
|
||||
for (i = 0; i < n && p.pred(xs.get(i)); ++i) {
|
||||
// empty
|
||||
}
|
||||
return drop(n, xs);
|
||||
}
|
||||
|
||||
public static <E> E head(LinkedList<E> xs) {
|
||||
return empty(xs) ? null : xs.element();
|
||||
}
|
||||
|
||||
public static <E> LinkedList<E> tail(LinkedList<E> xs) {
|
||||
if (empty(xs)) {
|
||||
return xs;
|
||||
}
|
||||
LinkedList<E> ys = new LinkedList<E>(xs);
|
||||
ys.remove();
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static <E> LinkedList<E> cons(E x, LinkedList<E> xs) {
|
||||
xs = empty(xs) ? new LinkedList<E>() : xs;
|
||||
xs.addFirst(x);
|
||||
return xs;
|
||||
}
|
||||
|
||||
public static <E> E first(List<E> xs) {
|
||||
return FP.empty(xs) ? null : xs.get(0);
|
||||
}
|
||||
|
||||
public static <E> E second(List<E> xs) {
|
||||
return FP.size(xs) < 2 ? null : xs.get(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the last element of a list
|
||||
*/
|
||||
public static <E> E last(List<E> xs) {
|
||||
return FP.empty(xs) ? null : xs.get(FP.lastIndex(xs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe return the index of the last element even if the list is NIL
|
||||
*
|
||||
* @return -1 if the list is NIL
|
||||
*/
|
||||
public static int lastIndex(List<?> xs) {
|
||||
return FP.empty(xs) ? -1 : xs.size() - 1;
|
||||
}
|
||||
|
||||
public static <E> E first(Collection<E> xs) {
|
||||
if (empty(xs)) {
|
||||
return null;
|
||||
}
|
||||
return xs.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe convert a collection to list even it's NIL
|
||||
*/
|
||||
public static <E> List<E> toList(Collection<? extends E> xs) {
|
||||
return empty(xs) ? new ArrayList<E>() : new ArrayList<E>(xs);
|
||||
}
|
||||
|
||||
public static <T> List<T> toList(T x) {
|
||||
return Collections.singletonList(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe convert an array to list even it's NIL
|
||||
*/
|
||||
public static <T> List<T> toList(T[] xs) {
|
||||
List<T> ys = new ArrayList<T>();
|
||||
if (!empty(xs)) {
|
||||
for (T x : xs) {
|
||||
ys.add(x);
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static List<Integer> toList(int[] xs) {
|
||||
List<Integer> ys = new ArrayList<Integer>();
|
||||
if (!empty(xs)) {
|
||||
for (int x : xs) {
|
||||
ys.add(x);
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static List<Long> toList(long[] xs) {
|
||||
List<Long> ys = new ArrayList<Long>();
|
||||
if (!empty(xs)) {
|
||||
for (long x : xs) {
|
||||
ys.add(x);
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static <E> List<Pair<Integer, E>> toList(SparseArray<E> xs) {
|
||||
List<Pair<Integer, E>> ys = new ArrayList<Pair<Integer, E>>();
|
||||
if (!empty(xs)) {
|
||||
for (int i = 0; i < xs.size(); ++i) {
|
||||
ys.add(Pair.create(xs.keyAt(i), xs.valueAt(i)));
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static List<Pair<Integer, Integer>> toList(SparseIntArray xs) {
|
||||
List<Pair<Integer, Integer>> ys = new ArrayList<Pair<Integer, Integer>>();
|
||||
if (!empty(xs)) {
|
||||
for (int i = 0; i < xs.size(); ++i) {
|
||||
ys.add(Pair.create(xs.keyAt(i), xs.valueAt(i)));
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static int[] toArray(List<Integer> xs) {
|
||||
int n = length(xs);
|
||||
int[] ys = new int[n];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
ys[i] = xs.get(i);
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
public static List<Integer> toIntegerList(List<Long> list) {
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
List<Integer> intList = new ArrayList<Integer>();
|
||||
for (Long value : list) {
|
||||
intList.add(value.intValue());
|
||||
}
|
||||
return intList;
|
||||
}
|
||||
|
||||
public static List<Long> toLongList(List<Integer> list) {
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
List<Long> intList = new ArrayList<Long>();
|
||||
for (Integer value : list) {
|
||||
intList.add(value.longValue());
|
||||
}
|
||||
return intList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe reference to a list even it's NIL
|
||||
*
|
||||
* @return reference to the list if it's not NIL, or an empty list.
|
||||
*/
|
||||
public static <E> List<E> ref(List<E> xs) {
|
||||
return xs == null ? new ArrayList<E>() : xs;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <E> E[] ref(E[] xs) {
|
||||
return xs == null ? (E[]) new Object[]{} : xs;
|
||||
}
|
||||
|
||||
public static int[] ref(int[] xs) {
|
||||
return xs == null ? new int[]{} : xs;
|
||||
}
|
||||
|
||||
public static String ref(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe zipper
|
||||
*/
|
||||
public static <A, B> List<Pair<A, B>> zip(List<A> as, List<B> bs) {
|
||||
List<Pair<A, B>> xs = new ArrayList<Pair<A, B>>();
|
||||
if (!empty(as) && !empty(bs)) {
|
||||
Iterator<A> a = as.iterator();
|
||||
Iterator<B> b = bs.iterator();
|
||||
while (a.hasNext() && b.hasNext()) {
|
||||
xs.add(Pair.create(a.next(), b.next()));
|
||||
}
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe equal predicate
|
||||
*/
|
||||
public static boolean eq(Object a, Object b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
} else if (a == null) {
|
||||
return false;
|
||||
}
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
public static boolean isPrefixOf(String prefix, String s) {
|
||||
if (empty(prefix)) {
|
||||
return true;
|
||||
}
|
||||
if (empty(s)) {
|
||||
return false;
|
||||
}
|
||||
return s.startsWith(prefix);
|
||||
}
|
||||
|
||||
public static <E> boolean isPrefixOf(List<E> prefix, List<E> xs) {
|
||||
if (empty(prefix)) {
|
||||
return true;
|
||||
}
|
||||
if (empty(xs)) {
|
||||
return false;
|
||||
}
|
||||
return eq(prefix, take(length(prefix), xs));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> void convert(T[] dst, Object[] src) {
|
||||
for (int i = 0; i < src.length; i++) {
|
||||
dst[i] = (T) src;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe concatenation
|
||||
* Note that the first list is MUTATED if it isn't empty.
|
||||
*/
|
||||
public static <T> List<T> concat(List<T> xs, List<T> ys) {
|
||||
List<T> zs = ref(xs);
|
||||
zs.addAll(ref(ys));
|
||||
return zs;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] concat(T[] xs, T[] ys) {
|
||||
T[] zs = (T[]) new Object[length(xs) + length(ys)];
|
||||
int i = 0;
|
||||
for (T x : xs) {
|
||||
zs[i++] = x;
|
||||
}
|
||||
for (T y : ys) {
|
||||
zs[i++] = y;
|
||||
}
|
||||
return zs;
|
||||
}
|
||||
|
||||
public static int[] concat(int[] xs, int[] ys) {
|
||||
int[] zs = new int[length(xs) + length(ys)];
|
||||
int i = 0;
|
||||
for (int x : xs) {
|
||||
zs[i++] = x;
|
||||
}
|
||||
for (int y : ys) {
|
||||
zs[i++] = y;
|
||||
}
|
||||
return zs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe union, O(N^2) time bound
|
||||
* Note that
|
||||
* 1. the first list is mutated if it isn't empty.
|
||||
* 2. Only the duplicated elements in the second list is removed, those duplicated ones in first is kept.
|
||||
* If the order needn't be kept, we strongly recommend to use set instead!
|
||||
*/
|
||||
public static <T> List<T> unionBy(final Eq<T> cmp, List<T> xs, List<T> ys) {
|
||||
ys = ref(ys);
|
||||
if (empty(xs)) {
|
||||
return ys;
|
||||
}
|
||||
for (T y : ys) {
|
||||
boolean e = false;
|
||||
for (T x : xs) {
|
||||
if (cmp.eq(x, y)) {
|
||||
e = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!e) {
|
||||
xs.add(y);
|
||||
}
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
public static <T> List<T> union(List<T> xs, List<T> ys) {
|
||||
return unionBy(new Eq<T>() {
|
||||
@Override
|
||||
public boolean eq(T x, T y) {
|
||||
return FP.eq(x, y);
|
||||
}
|
||||
}, xs, ys);
|
||||
}
|
||||
|
||||
/**
|
||||
* list difference xs \\ ys
|
||||
* invariant: (xs ++ ys) \\ xs == ys
|
||||
*/
|
||||
public static <T> List<T> diffBy(final Eq<T> eq, List<T> xs, List<T> ys) {
|
||||
List<T> zs = toList(xs);
|
||||
for (T y : ys) {
|
||||
zs = delBy(eq, zs, y);
|
||||
}
|
||||
return zs;
|
||||
}
|
||||
|
||||
public static <T> List<T> diff(List<T> xs, List<T> ys) {
|
||||
return diffBy(new Eq<T>() {
|
||||
@Override
|
||||
public boolean eq(T x, T y) {
|
||||
return FP.eq(x, y);
|
||||
}
|
||||
}, xs, ys);
|
||||
}
|
||||
|
||||
/**
|
||||
* mapping
|
||||
*/
|
||||
public static <A, B> List<B> map(UnaryFunc<B, A> f, List<A> xs) {
|
||||
List<B> ys = new ArrayList<B>();
|
||||
for (A x : ref(xs)) {
|
||||
ys.add(f.apply(x));
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
/**
|
||||
* filtering
|
||||
*/
|
||||
public static <E> List<E> filter(Pred<E> p, List<E> xs) {
|
||||
List<E> ys = new ArrayList<E>();
|
||||
for (E x : xs) {
|
||||
if (p.pred(x)) {
|
||||
ys.add(x);
|
||||
}
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
/**
|
||||
* folding left
|
||||
*/
|
||||
public static <S, E> S fold(BinaryFunc<S, S, E> f, S s, Collection<E> xs) {
|
||||
if (!empty(xs)) {
|
||||
for (E x : xs) {
|
||||
s = f.apply(s, x);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* ordered insertion.
|
||||
* O(\lg N) algorithm as it uses binary search
|
||||
* Please ensure the list support random access, so that the binary search make sense.
|
||||
*/
|
||||
public static <E> List<E> insert(Comparator<E> cmp, E x, List<E> xs) {
|
||||
int pos = Collections.binarySearch(xs, x, cmp);
|
||||
pos = (pos < 0) ? -pos - 1 : pos;
|
||||
xs.add(-pos - 1, x);
|
||||
return xs;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper to java.util.Collections.sort().
|
||||
* for chained style usage.
|
||||
*/
|
||||
public static <E> List<E> sort(Comparator<E> cmp, List<E> xs) {
|
||||
xs = ref(xs);
|
||||
try {
|
||||
Collections.sort(xs, cmp);
|
||||
} catch (Exception e) {
|
||||
//YLog.error(FP.class, "Failed to sort %s for %s", xs, e);
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
public static int sum(Integer[] xs) {
|
||||
int n = 0;
|
||||
for (int x : xs) {
|
||||
n += x;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public static long sum(Long[] xs) {
|
||||
long n = 0;
|
||||
for (long x : xs) {
|
||||
n += x;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int sum(List<Integer> xs) {
|
||||
int n = 0;
|
||||
for (int x : xs) {
|
||||
n += x;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public static long sum(List<Long> xs, Long a) {
|
||||
long n = 0;
|
||||
for (long x : xs) {
|
||||
n += x;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int ord(boolean x) {
|
||||
return x ? 1 : 0;
|
||||
}
|
||||
|
||||
public static int ord(Integer x) {
|
||||
return x == null ? 0 : x;
|
||||
}
|
||||
|
||||
public static <E> List<E> replicate(int n, E x) {
|
||||
List<E> xs = new ArrayList<E>();
|
||||
while (n-- > 0) {
|
||||
xs.add(x);
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
public static <E> List<E> replicate(int n, Callable<E> gen) {
|
||||
List<E> xs = new ArrayList<E>();
|
||||
try {
|
||||
while (n-- > 0) {
|
||||
xs.add(gen.call());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
public static interface UnaryFunc<R, A> {
|
||||
R apply(A x);
|
||||
}
|
||||
|
||||
public static interface BinaryFunc<R, A, B> {
|
||||
R apply(A a, B b);
|
||||
}
|
||||
|
||||
/* Predicate */
|
||||
public static abstract class Pred<A> implements UnaryFunc<Boolean, A> {
|
||||
@Override
|
||||
public Boolean apply(A x) {
|
||||
return pred(x);
|
||||
}
|
||||
|
||||
public abstract boolean pred(A x);
|
||||
}
|
||||
|
||||
/* Equality */
|
||||
public static abstract class Eq<A> implements BinaryFunc<Boolean, A, A> {
|
||||
@Override
|
||||
public Boolean apply(A x, A y) {
|
||||
return eq(x, y);
|
||||
}
|
||||
|
||||
public abstract boolean eq(A x, A y);
|
||||
}
|
||||
|
||||
public static class Tuple<A, B, C> {
|
||||
public A a;
|
||||
public B b;
|
||||
public C c;
|
||||
|
||||
public Tuple(A x, B y, C z) {
|
||||
a = x;
|
||||
b = y;
|
||||
c = z;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tree based Map (ordable)
|
||||
*/
|
||||
public static class M {
|
||||
public static <K, V> List<Pair<K, V>> toList(Map<K, V> m) {
|
||||
List<Pair<K, V>> xs = new ArrayList<Pair<K, V>>();
|
||||
if (!empty(m)) {
|
||||
for (Entry<K, V> e : m.entrySet()) {
|
||||
xs.add(Pair.create(e.getKey(), e.getValue()));
|
||||
}
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
|
||||
|
||||
public static <K extends Comparable<K>, V> Map<K, V> fromList(List<Pair<K, V>> xs) {
|
||||
Map<K, V> m = new TreeMap<K, V>();
|
||||
if (!empty(xs)) {
|
||||
for (Pair<K, V> p : xs) {
|
||||
m.put(p.first, p.second);
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
public static <V> Map<Integer, V> fromList(SparseArray<V> xs) {
|
||||
Map<Integer, V> m = new TreeMap<Integer, V>();
|
||||
if (!empty(xs)) {
|
||||
for (int i = 0; i < xs.size(); ++i) {
|
||||
m.put(xs.keyAt(i), xs.valueAt(i));
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
public static <V> List<V> values(SparseArray<V> m) {
|
||||
List<V> xs = new ArrayList<V>();
|
||||
int i, n = size(m);
|
||||
for (i = 0; i < n; ++i) {
|
||||
xs.add(m.valueAt(i));
|
||||
}
|
||||
return xs;
|
||||
}
|
||||
}
|
||||
}
|
205
library/src/main/java/com/chwl/library/utils/FormatUtils.java
Normal file
205
library/src/main/java/com/chwl/library/utils/FormatUtils.java
Normal file
@@ -0,0 +1,205 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
|
||||
import com.chwl.library.R;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* <p> 格式化工具类 </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2018/1/9
|
||||
*/
|
||||
public class FormatUtils {
|
||||
|
||||
public static String formatBigDecimal(double bigDecimal) {
|
||||
try {
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#,##0.##");
|
||||
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
|
||||
decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
return decimalFormat.format(bigDecimal);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return "0.00";
|
||||
}
|
||||
|
||||
public static String formatBigInteger(double bigDecimal) {
|
||||
try {
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#,##0");
|
||||
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
|
||||
decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
return decimalFormat.format(bigDecimal);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* 把过长的金额类数字,转换成两位小数带万,亿,兆 缩写
|
||||
* 10000.00 -> 1.00万 丢掉两位小数后面的小数
|
||||
*
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
public static String formatToShortDown(double num) {
|
||||
return formatToShort(num, RoundingMode.DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把过长的金额类数字,转换成两位小数带万,亿,兆 缩写
|
||||
* 10000.00 -> 1.00万 四舍五入两位小数后面的小数
|
||||
*
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
public static String formatToShortHalfUp(double num) {
|
||||
return formatToShort(num, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把过长的金额类数字,转换成两位小数带万,亿,兆 缩写
|
||||
* 10000.00 -> 1.00万
|
||||
*
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
public static String formatToShort(double num, RoundingMode roundingMode) {
|
||||
try {
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#,##0.00");
|
||||
decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
decimalFormat.setRoundingMode(roundingMode);
|
||||
double wan = Math.pow(10.0f, 4);
|
||||
double yi = Math.pow(10.0f, 8);
|
||||
double zhao = Math.pow(10.0f, 12);
|
||||
double res = num;
|
||||
if (Math.abs(num / wan) < 1) {
|
||||
res = num;
|
||||
return decimalFormat.format(res);
|
||||
} else if (Math.abs(num / wan) >= 1 && Math.abs(num / yi) < 1) {
|
||||
res = num / wan;
|
||||
return decimalFormat.format(res) + ResUtil.getString(R.string.xchat_android_library_utils_formatutils_01);
|
||||
} else if (Math.abs(num / yi) >= 1 && Math.abs(num / zhao) < 1) {
|
||||
res = num / yi;
|
||||
return decimalFormat.format(res) + ResUtil.getString(R.string.xchat_android_library_utils_formatutils_02);
|
||||
} else {
|
||||
res = num / zhao;
|
||||
return decimalFormat.format(res) + ResUtil.getString(R.string.xchat_android_library_utils_formatutils_03);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return ResUtil.getString(R.string.xchat_android_library_utils_formatutils_04);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将手机号码 显示成带隐私形式
|
||||
*
|
||||
* @param phoneNum
|
||||
* @param beginIndex 开始用* 号替换位置
|
||||
* @param endIndex 结束用* 号替换位置
|
||||
* @return
|
||||
*/
|
||||
public static String formatPhoneNumWithPrivacy(String phoneNum, int beginIndex, int endIndex) {
|
||||
StringBuffer resSB = new StringBuffer();
|
||||
for (int i = 0; i < phoneNum.length(); i++) {
|
||||
if (i >= beginIndex && i < endIndex) {
|
||||
resSB.append("*");
|
||||
} else {
|
||||
resSB.append(phoneNum.charAt(i));
|
||||
}
|
||||
}
|
||||
return resSB.toString();
|
||||
}
|
||||
|
||||
//PK值相关格式化方式,统一方法,方便以后更改样式
|
||||
public static String formatPKValue(long value) {
|
||||
// if (value >= 1000000) return (value / 100) / 100f + ResUtil.getString(R.string.xchat_android_library_utils_formatutils_05);
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
|
||||
public static String formatBigNum(String num) {
|
||||
if (StringUtils.isEmpty(num)) {
|
||||
// 数据为空直接返回0
|
||||
return "0";
|
||||
}
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!StringUtils.isNumeric(num)) {
|
||||
// 如果数据不是数字则直接返回0
|
||||
return "0";
|
||||
}
|
||||
|
||||
BigDecimal b0 = new BigDecimal("100000");
|
||||
BigDecimal b1 = new BigDecimal("10000");
|
||||
BigDecimal b3 = new BigDecimal(num);
|
||||
|
||||
String formatedNum = "";//输出结果
|
||||
String unit = "";//单位
|
||||
|
||||
if (b3.compareTo(b0) < 0) {
|
||||
sb.append(b3);
|
||||
} else if (b3.compareTo(b0) == 0 || b3.compareTo(b0) > 0) {
|
||||
formatedNum = String.valueOf(b3.divide(b1));
|
||||
unit = "W";
|
||||
}
|
||||
if (!"".equals(formatedNum)) {
|
||||
int i = formatedNum.indexOf(".");
|
||||
if (i == -1) {
|
||||
sb.append(formatedNum).append(unit);
|
||||
} else {
|
||||
String data = formatedNum.substring(0, i);
|
||||
sb.append(data).append(unit);
|
||||
}
|
||||
}
|
||||
if (sb.length() == 0)
|
||||
return "0";
|
||||
return sb.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatToShortKMHalfUp(double num) {
|
||||
return formatToShortKM(num, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
public static String formatToShortKMDown(double num) {
|
||||
return formatToShortKM(num, RoundingMode.DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把过长的金额类数字,转换成两位小数带K,M 缩写
|
||||
*
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
public static String formatToShortKM(double num, RoundingMode roundingMode) {
|
||||
try {
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#.#");
|
||||
decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
decimalFormat.setRoundingMode(roundingMode);
|
||||
double K = Math.pow(10.0f, 3);
|
||||
double M = Math.pow(10.0f, 6);
|
||||
double resK = num / K;
|
||||
if (resK < 1) {
|
||||
return decimalFormat.format(num);
|
||||
} else {
|
||||
double resM = num / M;
|
||||
if (resM < 1) {
|
||||
return decimalFormat.format(resK) + "K";
|
||||
} else {
|
||||
return decimalFormat.format(resM) + "M";
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return String.valueOf(num);
|
||||
}
|
||||
}
|
||||
}
|
27
library/src/main/java/com/chwl/library/utils/IOUtils.java
Normal file
27
library/src/main/java/com/chwl/library/utils/IOUtils.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <p> </p>
|
||||
*
|
||||
* @author jiahui
|
||||
* @date 2018/2/5
|
||||
*/
|
||||
public class IOUtils {
|
||||
private IOUtils() {
|
||||
}
|
||||
|
||||
public static void close(Closeable... closeables) {
|
||||
if (closeables == null || closeables.length == 0) return;
|
||||
try {
|
||||
for (Closeable closeable : closeables) {
|
||||
if (closeable != null)
|
||||
closeable.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
63
library/src/main/java/com/chwl/library/utils/ImeUtil.java
Normal file
63
library/src/main/java/com/chwl/library/utils/ImeUtil.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.chwl.library.utils.asynctask.ScheduledTask;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: xuduo
|
||||
* Date: 8/15/13
|
||||
* Time: 6:33 PM
|
||||
* To change this template use File | Settings | File Templates.
|
||||
* this is a UIUtility class
|
||||
*/
|
||||
public class ImeUtil {
|
||||
|
||||
public static void hideIME(Activity activity) {
|
||||
View view = activity.getCurrentFocus();
|
||||
if (null != view)
|
||||
hideIME(activity, view);
|
||||
}
|
||||
|
||||
public static void hideIME(Activity activity, View v) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
public static void showIME(Activity activity, View view) {
|
||||
if (null == view) {
|
||||
view = activity.getCurrentFocus();
|
||||
if (null == view)
|
||||
return;
|
||||
}
|
||||
((InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE)).showSoftInput(view, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
|
||||
public static void showIME(Activity activity, View view, int flag) {
|
||||
if (null == view) {
|
||||
view = activity.getCurrentFocus();
|
||||
if (null == view)
|
||||
return;
|
||||
}
|
||||
((InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE)).showSoftInput(view, flag);
|
||||
}
|
||||
|
||||
public static void showIMEDelay(final Activity activity, final View view, long time) {
|
||||
ScheduledTask.getInstance().scheduledDelayed(new Runnable() {
|
||||
public void run() {
|
||||
showIME(activity, view);
|
||||
}
|
||||
}, time);
|
||||
}
|
||||
|
||||
public static void hideIME(Context context, EditText editText) {
|
||||
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
}
|
255
library/src/main/java/com/chwl/library/utils/JavaUtil.java
Normal file
255
library/src/main/java/com/chwl/library/utils/JavaUtil.java
Normal file
@@ -0,0 +1,255 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.chwl.library.R;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 数据类型转换
|
||||
* @author hm
|
||||
*/
|
||||
public class JavaUtil {
|
||||
|
||||
public static long str2long(String str){
|
||||
if(!TextUtils.isEmpty(str)){
|
||||
long result = 0;
|
||||
try {
|
||||
result = Long.parseLong(str);
|
||||
} catch (Exception e) {
|
||||
Log.e("JavaUtil",ResUtil.getString(R.string.xchat_android_library_utils_javautil_01));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Integer str2int(String str) {
|
||||
int result = 0;
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
try {
|
||||
result = Integer.parseInt(str);
|
||||
} catch (Exception e) {
|
||||
//数据转化问题
|
||||
Log.e("JavaUtil",ResUtil.getString(R.string.xchat_android_library_utils_javautil_02));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Double str2double(String str) {
|
||||
double result = 0.0;
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
try {
|
||||
result = Double.parseDouble(str);
|
||||
} catch (Exception e) {
|
||||
//数据转化问题
|
||||
Log.e("JavaUtil",ResUtil.getString(R.string.xchat_android_library_utils_javautil_03));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public static String str2double2len(String str){
|
||||
try {
|
||||
DecimalFormat df = new DecimalFormat("######0.00");
|
||||
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
String format = df.format(str2double(str));
|
||||
return format;
|
||||
} catch (Exception e) {
|
||||
Log.e("JavaUtil",ResUtil.getString(R.string.xchat_android_library_utils_javautil_04));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String str2double0len(String str){
|
||||
try {
|
||||
DecimalFormat df = new DecimalFormat("######0");
|
||||
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
String format = df.format(str2double(str));
|
||||
return format;
|
||||
} catch (Exception e) {
|
||||
Log.e("JavaUtil",ResUtil.getString(R.string.xchat_android_library_utils_javautil_05));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Float str2flaot(String str) {
|
||||
float result = 0f;
|
||||
if (!TextUtils.isEmpty(str)) {
|
||||
try {
|
||||
result = Float.parseFloat(str);
|
||||
} catch (Exception e) {
|
||||
Log.e("JavaUtil",ResUtil.getString(R.string.xchat_android_library_utils_javautil_06));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* float类型保留两位小数
|
||||
*/
|
||||
public static float float2(float num) {
|
||||
try {
|
||||
BigDecimal b = new BigDecimal(num);
|
||||
float f = b.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
|
||||
return f;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* double类型保留两位小数
|
||||
*/
|
||||
public static double double2(double f) {
|
||||
BigDecimal b = new BigDecimal(f);
|
||||
double df = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||
return df;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保留两位小数,不四舍五入
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static String formatDecimal (double value){
|
||||
final DecimalFormat formatter = new DecimalFormat();
|
||||
formatter.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
formatter.setMaximumFractionDigits(2);
|
||||
formatter.setGroupingSize(0);
|
||||
formatter.setRoundingMode(RoundingMode.FLOOR);
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
public static Float getMin(List<Float> list) {
|
||||
Float min = list.get(0);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (min > list.get(i))
|
||||
min = list.get(i);
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
public static Float getMax(List<Float> list) {
|
||||
Float max = list.get(0);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (max < list.get(i))
|
||||
max = list.get(i);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* desc:将数组转为64编码
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String objToBase64Str(Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
// 创建字节输出流
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = null;
|
||||
String base64Str = null;
|
||||
try {
|
||||
oos = new ObjectOutputStream(baos);
|
||||
// 将对象放到OutputStream中
|
||||
oos.writeObject(obj);
|
||||
base64Str = new String(Base64.encode(baos.toByteArray(), 0));
|
||||
} catch (IOException e) {
|
||||
base64Str = null;
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (baos != null) {
|
||||
baos.close();
|
||||
}
|
||||
if (oos != null) {
|
||||
oos.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
base64Str = null;
|
||||
}
|
||||
}
|
||||
return base64Str;
|
||||
}
|
||||
|
||||
/**
|
||||
* desc:将64编码的数据转为对象
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
public static Object base64StrToObj(String data) {
|
||||
Object obj = null;
|
||||
if (TextUtils.isEmpty(data)) {
|
||||
return obj;
|
||||
}
|
||||
// 读取字节
|
||||
byte[] userByte = Base64.decode(data, 0);
|
||||
// 封装到字节流
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(userByte);
|
||||
ObjectInputStream ois = null;
|
||||
try {
|
||||
// 再次封装
|
||||
ois = new ObjectInputStream(bais);
|
||||
// 读取对象
|
||||
obj = ois.readObject();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (bais != null) {
|
||||
bais.close();
|
||||
}
|
||||
if (ois != null) {
|
||||
ois.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
obj = null;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static int weekStr2int(String str) {
|
||||
int i = 0;
|
||||
if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_07))) {
|
||||
i = 1;
|
||||
} else if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_08))) {
|
||||
i = 2;
|
||||
} else if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_09))) {
|
||||
i = 3;
|
||||
} else if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_010))) {
|
||||
i = 4;
|
||||
} else if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_011))) {
|
||||
i = 5;
|
||||
} else if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_012))) {
|
||||
i = 6;
|
||||
} else if (str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_013)) || str.equals(ResUtil.getString(R.string.xchat_android_library_utils_javautil_014))) {
|
||||
i = 7;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
17
library/src/main/java/com/chwl/library/utils/ListUtils.java
Normal file
17
library/src/main/java/com/chwl/library/utils/ListUtils.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p> </p>
|
||||
* Created by Administrator on 2017/11/16.
|
||||
*/
|
||||
public class ListUtils {
|
||||
|
||||
public static boolean isListEmpty(List list) {
|
||||
return (list == null || list.size() == 0);
|
||||
}
|
||||
public static boolean isNotEmpty(List list) {
|
||||
return (list != null && !list.isEmpty());
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import com.chwl.library.utils.log.MLog;
|
||||
|
||||
/**
|
||||
* Created by xujiexing on 14-3-19.
|
||||
*/
|
||||
public class LogCallerUtils {
|
||||
public static final int VERBOSE = 0;
|
||||
public static final int DEBUG = 1;
|
||||
public static final int INFO = 2;
|
||||
public static final int WARN = 3;
|
||||
public static final int ERROR = 4;
|
||||
private static final String TAG = "LogCallerUtils";
|
||||
|
||||
public static void logStack(String msg) {
|
||||
logStack(msg, VERBOSE);
|
||||
}
|
||||
|
||||
public static void logStack(String msg, int level) {
|
||||
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(msg + ", caller stack = [ ");
|
||||
for (StackTraceElement e : stackTraceElements) {
|
||||
sb.append(e.toString() + ", ");
|
||||
}
|
||||
String logs = sb.substring(0, sb.length() - 2) + " ]";
|
||||
switch (level) {
|
||||
case DEBUG:
|
||||
MLog.debug(TAG, logs);
|
||||
break;
|
||||
case INFO:
|
||||
MLog.info(TAG, logs);
|
||||
break;
|
||||
case WARN:
|
||||
MLog.warn(TAG, logs);
|
||||
break;
|
||||
case ERROR:
|
||||
MLog.error(TAG, logs);
|
||||
break;
|
||||
default:
|
||||
MLog.verbose(TAG, logs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
18
library/src/main/java/com/chwl/library/utils/LogUtil.java
Normal file
18
library/src/main/java/com/chwl/library/utils/LogUtil.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.chwl.library.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.chwl.library.utils.config.BasicConfig;
|
||||
|
||||
/**
|
||||
* create by lvzebiao @2019/11/20
|
||||
*/
|
||||
public class LogUtil {
|
||||
|
||||
public static void print(String msg) {
|
||||
if (BasicConfig.INSTANCE.isDebuggable()) {
|
||||
Log.e("mouse_debug", msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user