66陪玩-origin

This commit is contained in:
oujunhui
2020-04-02 10:43:40 +08:00
commit 2f9d26fd7e
6758 changed files with 480551 additions and 0 deletions

3
library/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/build
*.iml
.DS_Sotre

89
library/build.gradle Normal file
View File

@@ -0,0 +1,89 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dataBinding {
enabled = true
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
staging {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
def supportLibraryVersion = "27.1.1"
def glideVersion = "4.4.0"
def retrofitVersion = "2.3.0"
def okhttp3 = "3.12.0"
def okio = "1.16.0"
def rxjava_adapter = "2.3.0"
def gson_converter = "2.3.0"
def rxjava = "2.1.7"
def rxjava_android = "2.0.1"
def rxlifecycle = "2.1.0"
def loggerVersion = "2.1.1"
def qiniu = "7.3.15"
def SmartRefreshLayoutVersion = "1.0.3"
def eventbusVersion = "3.0.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
api "com.android.support:appcompat-v7:${supportLibraryVersion}"
api "com.android.support:recyclerview-v7:${supportLibraryVersion}"
api "com.android.support:design:${supportLibraryVersion}"
api "com.squareup.retrofit2:retrofit:${retrofitVersion}"
api "com.squareup.okhttp3:okhttp:${okhttp3}"
api "com.squareup.okhttp3:logging-interceptor:${okhttp3}"
api "com.squareup.retrofit2:adapter-rxjava2:${rxjava_adapter}"
api "com.squareup.retrofit2:converter-gson:${gson_converter}"
api "com.squareup.okio:okio:${okio}"
api "com.scwang.smartrefresh:SmartRefreshLayout:${SmartRefreshLayoutVersion}"
api "com.scwang.smartrefresh:SmartRefreshHeader:${SmartRefreshLayoutVersion}"
api "io.reactivex.rxjava2:rxjava:${rxjava}"
api "io.reactivex.rxjava2:rxandroid:${rxjava_android}"
api "com.trello.rxlifecycle2:rxlifecycle:${rxlifecycle}"
api "com.trello.rxlifecycle2:rxlifecycle-components:${rxlifecycle}"
api "com.trello.rxlifecycle2:rxlifecycle-android:${rxlifecycle}"
api "com.github.bumptech.glide:glide:${glideVersion}"
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
api "com.orhanobut:logger:${loggerVersion}"
api "com.qiniu:qiniu-android-sdk:${qiniu}"
api "org.greenrobot:eventbus:${eventbusVersion}"
}

21
library/proguard-rules.pro vendored Normal file
View 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

View File

@@ -0,0 +1,26 @@
package com.yizhuan.xchat_android_library;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.yizhuan.xchat_android_library", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yizhuan.xchat_android_library">
</manifest>

View File

@@ -0,0 +1,25 @@
package com.yizhuan.xchat_android_library;
import android.app.Application;
import com.orhanobut.logger.AndroidLogAdapter;
import com.orhanobut.logger.Logger;
/**
* <p> </p>
*
* @author jiahui
* @date 2017/12/11
*/
public class CommonApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Logger.addLogAdapter(new AndroidLogAdapter() {
@Override
public boolean isLoggable(int priority, String tag) {
return BuildConfig.DEBUG;
}
});
}
}

View File

@@ -0,0 +1,226 @@
package com.yizhuan.xchat_android_library.adapters;
import android.support.annotation.NonNull;
import android.support.v7.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);
}
}

View File

@@ -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.yizhuan.xchat_android_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.yizhuan.xchat_android_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;
}
}
}

View File

@@ -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.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,15 @@
package com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,109 @@
package com.yizhuan.xchat_android_library.base;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import com.yizhuan.xchat_android_library.base.factory.BaseMvpProxy;
import com.yizhuan.xchat_android_library.base.factory.PresenterMvpFactory;
import com.yizhuan.xchat_android_library.base.factory.PresenterMvpFactoryImpl;
import com.yizhuan.xchat_android_library.base.factory.PresenterProxyInterface;
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
/**
* <p> 1. 子类的Presenter必须继承自AbstractMvpPresenter
* 2. 子类的View必须继承自IMvpBaseView
* </p>
*
* @author jiahui
* @date 2017/12/7
*/
public abstract class AbstractMvpActivity<V extends IMvpBaseView, P extends AbstractMvpPresenter<V>> extends RxAppCompatActivity
implements PresenterProxyInterface<V, P> {
public static final boolean DEBUG = false;
protected boolean afterOnSavedInstanceState = false;
private static final String TAG_LOG = "Super-mvp";
private static final String KEY_SAVE_PRESENTER = "key_save_presenter";
/** 创建代理对象传入默认的Presenter工厂 */
private BaseMvpProxy<V, P> mMvpProxy = new BaseMvpProxy<>(PresenterMvpFactoryImpl.<V, P>createFactory(getClass()));
private final String activityName = getClass().getName();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
logInfo(activityName + " V onCreate...");
logInfo(activityName + " V onCreate... mProxy=" + mMvpProxy);
logInfo(activityName + " V onCreate... this=" + this.hashCode());
if (savedInstanceState != null) {
mMvpProxy.onRestoreInstanceState(savedInstanceState.getBundle(KEY_SAVE_PRESENTER));
}
}
@Override
protected void onStart() {
super.onStart();
logInfo(activityName + " V onStart...");
mMvpProxy.onStart();
}
@Override
protected void onResume() {
super.onResume();
logInfo(activityName + " V onResume...");
mMvpProxy.onResume((V) this);
afterOnSavedInstanceState = false;
}
@Override
protected void onPause() {
mMvpProxy.onPause();
super.onPause();
logInfo(activityName + " V onPause...");
}
@Override
protected void onStop() {
mMvpProxy.onStop();
super.onStop();
logInfo(activityName + " V onStop...");
}
@Override
protected void onDestroy() {
mMvpProxy.onDestroy();
super.onDestroy();
logInfo(activityName + " V onDestroy...");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
logInfo(activityName + " V onSaveInstanceState...");
outState.putBundle(KEY_SAVE_PRESENTER, mMvpProxy.onSaveInstanceState());
afterOnSavedInstanceState = true;
}
@Override
public void setPresenterFactory(PresenterMvpFactory<V, P> presenterFactory) {
logInfo(activityName + " V setPresenterFactory...");
mMvpProxy.setPresenterFactory(presenterFactory);
}
@Override
public PresenterMvpFactory<V, P> getPresenterFactory() {
logInfo(activityName + " V getPresenterFactory...");
return mMvpProxy.getPresenterFactory();
}
@Override
public P getMvpPresenter() {
logInfo(activityName + " V getMvpPresenter...");
return mMvpProxy.getMvpPresenter();
}
private void logInfo(String msg) {
if (DEBUG)
Log.e(TAG_LOG, msg);
}
}

View File

@@ -0,0 +1,105 @@
package com.yizhuan.xchat_android_library.base;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import com.yizhuan.xchat_android_library.base.factory.BaseMvpProxy;
import com.yizhuan.xchat_android_library.base.factory.PresenterMvpFactory;
import com.yizhuan.xchat_android_library.base.factory.PresenterMvpFactoryImpl;
import com.yizhuan.xchat_android_library.base.factory.PresenterProxyInterface;
import com.trello.rxlifecycle2.components.support.RxFragment;
/**
* <p> 1. 子类的Presenter必须继承自AbstractMvpPresenter
* 2. 子类的View必须继承自IMvpBaseView
* </p>
*
* @author jiahui
* @date 2017/12/8
*/
public class AbstractMvpFragment<V extends IMvpBaseView, P extends AbstractMvpPresenter<V>> extends RxFragment
implements PresenterProxyInterface<V, P> {
protected final String TAG = getClass().getSimpleName();
private static final String TAG_LOG = "Super-mvp";
private static final String KEY_SAVE_PRESENTER = "key_save_presenter";
/** 创建代理对象传入默认的Presenter工厂 */
private BaseMvpProxy<V, P> mMvpProxy = new BaseMvpProxy<>(PresenterMvpFactoryImpl.<V, P>createFactory(getClass()));
private String mFragmentName = getClass().getName();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
logInfo(mFragmentName + " V onCreate...");
logInfo(mFragmentName + " V onCreate... mProxy=" + mMvpProxy);
logInfo(mFragmentName + " V onCreate... this=" + this.hashCode());
if (savedInstanceState != null) {
mMvpProxy.onRestoreInstanceState(savedInstanceState.getBundle(KEY_SAVE_PRESENTER));
}
}
@Override
public void onStart() {
super.onStart();
logInfo(mFragmentName + " V onStart...");
mMvpProxy.onStart();
}
@Override
public void onResume() {
super.onResume();
logInfo(mFragmentName + " V onResume...");
mMvpProxy.onResume((V) this);
}
@Override
public void onPause() {
mMvpProxy.onPause();
super.onPause();
logInfo(mFragmentName + " V onPause...");
}
@Override
public void onStop() {
mMvpProxy.onStop();
super.onStop();
logInfo(mFragmentName + " V onStop...");
}
@Override
public void onDestroy() {
mMvpProxy.onDestroy();
super.onDestroy();
logInfo(mFragmentName + " V onDestroy...");
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
logInfo(mFragmentName + " V onSaveInstanceState...");
outState.putBundle(KEY_SAVE_PRESENTER, mMvpProxy.onSaveInstanceState());
}
@Override
public void setPresenterFactory(PresenterMvpFactory<V, P> presenterFactory) {
logInfo(mFragmentName + " V setPresenterFactory...");
mMvpProxy.setPresenterFactory(presenterFactory);
}
@Override
public PresenterMvpFactory<V, P> getPresenterFactory() {
logInfo(mFragmentName + " V getPresenterFactory...");
return mMvpProxy.getPresenterFactory();
}
@Override
public P getMvpPresenter() {
logInfo(mFragmentName + " V getMvpPresenter...");
return mMvpProxy.getMvpPresenter();
}
private void logInfo(String msg) {
if (AbstractMvpActivity.DEBUG)
Log.e(TAG_LOG, msg);
}
}

View File

@@ -0,0 +1,142 @@
package com.yizhuan.xchat_android_library.base;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.trello.rxlifecycle2.LifecycleProvider;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.OutsideLifecycleException;
import com.trello.rxlifecycle2.RxLifecycle;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
import io.reactivex.subjects.BehaviorSubject;
/**
* <p>MVP模式Present 基类 ,跟ActivityFragment等生命周期绑定</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被意外销毁时调用它的调用时机和ActivityFragmentView中的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 (AbstractMvpActivity.DEBUG)
Log.e(TAG, msg);
}
}

View File

@@ -0,0 +1,11 @@
package com.yizhuan.xchat_android_library.base;
/**
* <p> MVP 模式View 基类</p>
*
* @author jiahui
* @date 2017/12/7
*/
public interface IMvpBaseView {
}

View File

@@ -0,0 +1,12 @@
package com.yizhuan.xchat_android_library.base;
public enum PresenterEvent {
CREATE,
START,
RESUME,
PAUSE,
STOP,
DESTROY
}

View File

@@ -0,0 +1,144 @@
package com.yizhuan.xchat_android_library.base.factory;
import android.os.Bundle;
import android.util.Log;
import com.yizhuan.xchat_android_library.base.AbstractMvpActivity;
import com.yizhuan.xchat_android_library.base.AbstractMvpPresenter;
import com.yizhuan.xchat_android_library.base.IMvpBaseView;
/**
* <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("这个方法只能在getMvpPresenter()之前调用如果Presenter已经创建了则不能再更改");
}
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 (AbstractMvpActivity.DEBUG)
Log.e(TAG, msg);
}
}

View File

@@ -0,0 +1,19 @@
package com.yizhuan.xchat_android_library.base.factory;
import com.yizhuan.xchat_android_library.base.AbstractMvpPresenter;
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();
}

View File

@@ -0,0 +1,20 @@
package com.yizhuan.xchat_android_library.base.factory;
import com.yizhuan.xchat_android_library.base.AbstractMvpPresenter;
import com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,49 @@
package com.yizhuan.xchat_android_library.base.factory;
import com.yizhuan.xchat_android_library.base.AbstractMvpPresenter;
import com.yizhuan.xchat_android_library.base.IMvpBaseView;
/**
* <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("Presenter 创建失败,检查是否声明了@CreatePresenter(xxx.class)注解!!!----", e);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.yizhuan.xchat_android_library.base.factory;
import com.yizhuan.xchat_android_library.base.AbstractMvpPresenter;
import com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,35 @@
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,13 @@
package com.yizhuan.xchat_android_library.bindinglist;
/**
* Created by lvzebiao on 2018/10/23.
*/
public interface IItem {
/**
* 格子布局时需要用到默认返回1线性布局忽略即可
*/
int getSpanSize();
int getType();
}

View File

@@ -0,0 +1,33 @@
package com.yizhuan.xchat_android_library.bindinglist;
import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
/**
* 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);
}
}

View File

@@ -0,0 +1,366 @@
package com.yizhuan.xchat_android_library.bindinglist;
import android.databinding.ObservableArrayList;
import android.databinding.ObservableList;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lvzebiao on 2018/10/23.
*/
public class MultiTypeAdapter extends RecyclerView.Adapter<ItemViewHolder> {
private List<IItem> headersItems = new ArrayList<>();
public List<IItem> getHeadersItems() {
return headersItems;
}
private ObservableList<IItem> footersItems = new ObservableArrayList<>();
/**
* 此数据源表示真正的数据, 保留用于其他逻辑计算
*/
private List<IItem> dataItems;
public List<IItem> getData() {
return dataItems;
}
/**
* 此list表示RecyclerView显示的所有item即header+data+footer
*/
private ObservableList<IItem> allItems = new ObservableArrayList<>();
public List<IItem> getAllItems() {
return allItems;
}
private boolean canLoadMore = false;
public void setCanLoadMore(boolean canLoadMore) {
this.canLoadMore = canLoadMore;
}
private RecyclerView recyclerView;
private final WeakReferenceOnListChangedCallback callback = new WeakReferenceOnListChangedCallback(this);
private int variableId;
private boolean needItemClick;
public MultiTypeAdapter(List<IItem> 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);
}
/**
* 这方法不会清除header清除header调用
* {@link #clearAllItem()}
*/
public void clearData() {
//清除数据源,也要清除底部加载更多
dataItems.clear();
statusList.clear();
footersItems.clear();
hideStatus();
if (headersItems.size() == 0) {
allItems.clear();
} else {
// List<IItem> 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<IItem> list) {
dataItems.addAll(list);
int insertPos = allItems.size() - footersItems.size();
if (insertPos < 0) {
insertPos = 0;
}
allItems.addAll(insertPos, list);
}
public void addHeaderItem(IItem headerItem) {
headersItems.add(headerItem);
allItems.add(0, headerItem);
}
private IItem loadMoreItem;
public void setLoadMoreView(IItem 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);
}
}
}
private List<IItem> statusList = new ArrayList<>();
private IItem startLoadingItem;
/**
* 此方法并不是所有list都需要不需要则传null
* 只是客户端有个加载动画,所以添加
*/
public void startLoading(IItem item) {
if (item == null) {
return;
}
if (statusList.size() == 0) {
if (startLoadingItem == null) {
startLoadingItem = item;
}
statusList.add(startLoadingItem);
allItems.add(startLoadingItem);
}
}
private IItem netErrorItem;
/**
* 如果有数据不添加错误item
*/
public void setNetError(IItem 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);
}
}
}
private IItem emptyItem;
public void setEmpty(IItem 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.contains(startLoadingItem)) {
allItems.remove(startLoadingItem);
}
if (netErrorItem != null && allItems.contains(netErrorItem)) {
allItems.remove(netErrorItem);
}
if (emptyItem != null && allItems.contains(emptyItem)) {
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 IItem 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 IItem 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;
}
private OnItemClickListener listener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public interface OnItemClickListener {
void onClick(IItem item);
}
@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
if (this.recyclerView != null && allItems != null) {
allItems.removeOnListChangedCallback(callback);
}
this.recyclerView = null;
}
private static class WeakReferenceOnListChangedCallback extends ObservableList.OnListChangedCallback<ObservableList<IItem>> {
final WeakReference<MultiTypeAdapter> adapterRef;
WeakReferenceOnListChangedCallback(MultiTypeAdapter adapter) {
this.adapterRef = new WeakReference<>(adapter);
}
@Override
public void onChanged(ObservableList sender) {
MultiTypeAdapter adapter = adapterRef.get();
if (adapter == null) {
return;
}
ensureChangeOnMainThread();
adapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList sender, final int positionStart, final int itemCount) {
MultiTypeAdapter 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 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 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 adapter = adapterRef.get();
if (adapter == null) {
return;
}
ensureChangeOnMainThread();
adapter.notifyItemRangeRemoved(positionStart, itemCount);
}
}
static void ensureChangeOnMainThread() {
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
throw new IllegalStateException("You must only modify the ObservableList on the main thread.");
}
}
}

View File

@@ -0,0 +1,36 @@
/**
* 每个core实现类都应该继承此类
* 提供一些基础设施给子类使用
*/
package com.yizhuan.xchat_android_library.coremanager;
import android.content.Context;
import com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,257 @@
/**
*
*/
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,23 @@
/**
* 用于使用annotation实现监听某个client的某个回调
*/
package com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,31 @@
/**
* 用于使用annotation实现监听某个client的某个回调
*/
package com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,36 @@
/**
* 用于封装core层的异常
*/
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,93 @@
/**
* Core对象工厂。使用getCore前需要注册对应core接口实现类
* 此类是非线程安全的,必须在主线程调用
*/
package com.yizhuan.xchat_android_library.coremanager;
import com.yizhuan.xchat_android_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);
}
}
}

View File

@@ -0,0 +1,432 @@
/**
* 管理core对象的类。外部应该使用此类的接口来获取某个core对象
* 它使用CoreFactory来声成core对象实例。
* 上层未注册core实现类话注册一个默认实现调用init函数
* 此类不是线程安全的,应该只在主线程调用
*/
package com.yizhuan.xchat_android_library.coremanager;
import android.content.Context;
import android.text.TextUtils;
import com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,12 @@
/**
* 所有的core接口必须继承此接口
*/
package com.yizhuan.xchat_android_library.coremanager;
/**
* @author daixiang
*
*/
public interface IBaseCore {
}

View File

@@ -0,0 +1,12 @@
/**
* 所有的core client接口必须继承于此接口
*/
package com.yizhuan.xchat_android_library.coremanager;
/**
* @author daixiang
*
*/
public interface ICoreClient {
}

View File

@@ -0,0 +1,606 @@
package com.yizhuan.xchat_android_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();
}
}
}
}

View File

@@ -0,0 +1,88 @@
package com.yizhuan.xchat_android_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();
}
}

View File

@@ -0,0 +1,107 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,81 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,64 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,61 @@
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,11 @@
package com.yizhuan.xchat_android_library.list;
import java.util.List;
/**
* Created by lijun on 2014/11/7.
*/
public interface GroupItem extends ListItem {
public List<ListItem> getChildItems();
}

View File

@@ -0,0 +1,20 @@
package com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,32 @@
package com.yizhuan.xchat_android_library.list;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.util.AttributeSet;
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();
}
}

View File

@@ -0,0 +1,18 @@
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,41 @@
package com.yizhuan.xchat_android_library.manager;
import android.app.Application;
import android.os.Debug;
import android.support.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();
}
}

View File

@@ -0,0 +1,19 @@
package com.yizhuan.xchat_android_library.net;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* <p> </p>
*
* @author jiahui
* @date 2017/12/16
*/
public final class ErBanAllHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
//信任所有的证书
return true;
}
}

View File

@@ -0,0 +1,60 @@
package com.yizhuan.xchat_android_library.net.rxnet;
import android.content.Context;
import com.yizhuan.xchat_android_library.net.rxnet.manager.RxNetManager;
import com.yizhuan.xchat_android_library.utils.NullUtils;
/**
* <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);
}
private static void checkInstance() {
NullUtils.checkNull(mInstance, "请在项目中先调用RxNet.init()方法初始化!!!");
}
}

View File

@@ -0,0 +1,24 @@
package com.yizhuan.xchat_android_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);
}

View File

@@ -0,0 +1,179 @@
package com.yizhuan.xchat_android_library.net.rxnet.https;
import com.yizhuan.xchat_android_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];
}
}
}

View File

@@ -0,0 +1,53 @@
package com.yizhuan.xchat_android_library.net.rxnet.interceptor;
import android.content.Context;
import com.yizhuan.xchat_android_library.net.rxnet.utils.RxNetLog;
import com.yizhuan.xchat_android_library.net.rxnet.utils.RxNetWorkUtils;
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("没有网络,强制获取缓存!");
}
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();
}
}
}

View File

@@ -0,0 +1,39 @@
package com.yizhuan.xchat_android_library.net.rxnet.manager;
import android.content.Context;
import com.yizhuan.xchat_android_library.net.rxnet.interceptor.HttpCacheInterceptor;
import com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,232 @@
package com.yizhuan.xchat_android_library.net.rxnet.manager;
import android.content.Context;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.yizhuan.xchat_android_library.BuildConfig;
import com.yizhuan.xchat_android_library.net.rxnet.https.HttpsUtils;
import com.yizhuan.xchat_android_library.net.rxnet.utils.RxNetLog;
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 java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import okhttp3.Cache;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* <p> RxNet 管理类 </p>
*
* @author jiahui
* date 2017/12/4
*/
public final class RxNetManager {
private static final int DEFAULT_READ_TIME_OUT = 60000;
private static final int DEFAULT_WRITE_TIME_OUT = 60000;
private static final int DEFAULT_CONNECT_TIME_OUT = 30000;
private OkHttpClient mOkHttpClient;
private OkHttpClient.Builder mBuilder;
private CacheManager mCacheManager;
private Retrofit mRetrofit;
RxNetManager(Context context, String baseUrl, Cache cache, int readTimeout,
int writeTimeout, int connectTimeout, List<Interceptor> interceptors,
HttpsUtils.SSLParams sslParams, HostnameVerifier hostnameVerifier) {
mCacheManager = new CacheManager(context);
mBuilder = new OkHttpClient.Builder();
if (RxNetLog.DEBUG) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
RxNetLog.d("OKHttp-------%s", message);
}
});
loggingInterceptor.setLevel(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)
.addNetworkInterceptor(mCacheManager.getHttpCacheInterceptor())
.cache(cache != null ? cache : mCacheManager.getCache())
.connectionPool(new ConnectionPool(10, 2, TimeUnit.SECONDS))
;
// 无代理设置,防止被抓包
// 指定只要不是 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) {
}
});
}
//https 支持
if (sslParams != null) {
mBuilder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
}
if (hostnameVerifier != null) {
mBuilder.hostnameVerifier(hostnameVerifier);
}
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 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;
}
}
}

View File

@@ -0,0 +1,271 @@
package com.yizhuan.xchat_android_library.net.rxnet.model;
import android.os.Build;
import android.text.TextUtils;
import com.yizhuan.xchat_android_library.net.rxnet.RxNet;
import com.yizhuan.xchat_android_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 + '}';
}
}

View File

@@ -0,0 +1,51 @@
package com.yizhuan.xchat_android_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();
}
}

View File

@@ -0,0 +1,38 @@
package com.yizhuan.xchat_android_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));
}
}
}

View File

@@ -0,0 +1,36 @@
package com.yizhuan.xchat_android_library.net.rxnet.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
/**
* <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(context);
return info != null && info.isAvailable();
}
/**
* 获取活动网络信息
*
* @param context 上下文
* @return NetworkInfo
*/
public static NetworkInfo getActiveNetworkInfo(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo();
}
}

View File

@@ -0,0 +1,139 @@
package com.yizhuan.xchat_android_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);
}
}
}

View File

@@ -0,0 +1,514 @@
package com.yizhuan.xchat_android_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();
}
}
}

View File

@@ -0,0 +1,27 @@
package com.yizhuan.xchat_android_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
}
}

View File

@@ -0,0 +1,8 @@
package com.yizhuan.xchat_android_library.record;
public class SimpleRecordFailed implements ExtAudioRecorder.RecorderListener{
@Override
public void recordFailed(FailRecorder failRecorder) {
}
}

View File

@@ -0,0 +1,46 @@
package com.yizhuan.xchat_android_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);
}
});
}
}

View File

@@ -0,0 +1,61 @@
package com.yizhuan.xchat_android_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();
}
}

View File

@@ -0,0 +1,97 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,37 @@
package com.yizhuan.xchat_android_library.service;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.orhanobut.logger.Logger;
/**
* <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("开始后台任务......%d", jobId);
// StatisticManager.Instance().deleteLogFiles()
// .subscribe(aBoolean -> {
// Logger.i("执行删除日志文件" + (aBoolean ? "成功" : "失败"));
// });
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
int jobId = params.getJobId();
Logger.d("停止后台任务......%d", jobId);
return false;
}
}

View File

@@ -0,0 +1,58 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,107 @@
package com.yizhuan.xchat_android_library.swipeactivity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.trello.rxlifecycle2.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() {
Utils.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();
}
}
}
}

View File

@@ -0,0 +1,24 @@
package com.yizhuan.xchat_android_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();
}

View File

@@ -0,0 +1,59 @@
package com.yizhuan.xchat_android_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.yizhuan.xchat_android_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) {
Utils.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;
}
}

View File

@@ -0,0 +1,610 @@
package com.yizhuan.xchat_android_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.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.yizhuan.xchat_android_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);
}
}
}
}
}

View File

@@ -0,0 +1,105 @@
package com.yizhuan.xchat_android_library.swipeactivity;
import android.app.Activity;
import android.app.ActivityOptions;
import android.os.Build;
import java.lang.reflect.Method;
/**
* Created by hm
*/
public class Utils {
private Utils() {
}
/**
* 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) {
}
}
}

View File

@@ -0,0 +1,18 @@
package com.yizhuan.xchat_android_library.threadmgr;
import android.util.Log;
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", "单线程池并发过高执行抛弃策略");
}
}

View File

@@ -0,0 +1,18 @@
package com.yizhuan.xchat_android_library.threadmgr;
import android.util.Log;
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", "光晕并发过高执行抛弃策略");
}
}

View File

@@ -0,0 +1,17 @@
package com.yizhuan.xchat_android_library.threadmgr;
import android.support.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");
}
}

View File

@@ -0,0 +1,66 @@
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,55 @@
package com.yizhuan.xchat_android_library.utils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import com.yizhuan.xchat_android_library.utils.config.BasicConfig;
import com.yizhuan.xchat_android_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) ? "qingxun_official" : 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");
}
}

View File

@@ -0,0 +1,22 @@
package com.yizhuan.xchat_android_library.utils;
import android.content.Context;
import android.content.pm.PackageManager;
/**
* <p> </p>
*
* @author jiahui
* date 2018/2/28
*/
public class AppUtils {
public static int getVersionCode(Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,58 @@
package com.yizhuan.xchat_android_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);
}
}
}
}

View File

@@ -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.yizhuan.xchat_android_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;
}
}
}

View 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.yizhuan.xchat_android_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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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('&copy;') = 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);
}
}

View File

@@ -0,0 +1,24 @@
package com.yizhuan.xchat_android_library.utils;
/**
* Created by lijun on 2014/11/20.
*/
public class ColorUtil {
public static String color2HexString(int intColor) {
String strColor = String.format("#%06X", 0xFFFFFF & intColor);
return strColor;
}
public static float brightness(int color) {
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
int V = Math.max(b, Math.max(r, g));
return (V / 255.f);
}
}

View File

@@ -0,0 +1,14 @@
package com.yizhuan.xchat_android_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;
}
}

View File

@@ -0,0 +1,80 @@
package com.yizhuan.xchat_android_library.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
/**
* Created by chenran on 2017/10/21.
*/
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
/**
* 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.
*/
public static String getDeviceId(Context context) {
UUID uuid = null;
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
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
} else {
final String deviceId = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();
uuid = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// Write the value out to the prefs file
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString()).commit();
}
}
if (uuid == null) {
return "";
} else {
return uuid.toString();
}
}
}

View File

@@ -0,0 +1,909 @@
package com.yizhuan.xchat_android_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;
}
}
}

View File

@@ -0,0 +1,147 @@
package com.yizhuan.xchat_android_library.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import okio.Okio;
/**
* <p> 文件操作工具类 </p>
*
* @author jiahui
* @date 2018/2/5
*/
public final class FileUtils {
/**
* 获取文件扩展名
*
* @param filename 文件路径
* @return 扩展名zip,如果文件没有扩展名返回null
*/
public static String getExtensionName(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length() - 1))) {
return filename.substring(dot + 1);
}
}
return null;
}
/**
* 获取不带扩展名的文件名
*
* @param filename 目标文件路径
* @return 文件名
*/
public static String getFileNameNoEx(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int dot = filename.lastIndexOf('.');
if ((dot > -1) && (dot < (filename.length()))) {
return filename.substring(0, dot);
}
}
return filename;
}
/**
* 获取文件路径的最后文件名(不带扩展名)
*
* @param filename 目标文件路径
* @return 最后文件名(不带扩展名)
*/
public static String getLastFileNameNoEx(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int separator = filename.lastIndexOf(File.separator);
if (separator > -1 && separator < filename.length()) {
String lastName = filename.substring(separator + 1);
int dot = lastName.lastIndexOf('.');
if ((dot > -1) && (dot < (lastName.length()))) {
return lastName.substring(0, dot);
} else {
return lastName;
}
}
}
return filename;
}
/**
* 获取zip报里面的文件或文件夹列表集合
*
* @param zipFilePath zip路径
* @param bContainFolder 是否包含文件夹
* @param bContainFile 是否包含文件
* @return zip里面的文件列表集合
*/
public static List<File> getZipFileList(String zipFilePath, boolean bContainFolder, boolean bContainFile) {
List<File> fileList = new ArrayList<>();
ZipInputStream inZip = null;
try {
inZip = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry zipEntry;
String szName;
while ((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();
if (zipEntry.isDirectory()) {
szName = szName.substring(0, szName.length() - 1);
if (bContainFolder)
fileList.add(new File(szName));
} else {
if (bContainFile)
fileList.add(new File(szName));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.close(inZip);
}
return fileList;
}
public static void copy(String srcFilePath, String destFilePath) {
try {
File destFile = new File(destFilePath);
Okio.buffer(Okio.sink(destFile)).writeAll(Okio.buffer(Okio.source(new File(srcFilePath))));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void write(File destFile, String content) {
try {
Okio.buffer(Okio.appendingSink(destFile))
.writeUtf8(content + "\n\n")
.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void write(String destFile, String content) {
write(new File(destFile), content);
}
/**
* 获取文件名
*
* @param filepath
* @return
*/
public static String getFileNameFromPath(String filepath) {
if ((filepath != null) && (filepath.length() > 0)) {
int sep = filepath.lastIndexOf('/');
if ((sep > -1) && (sep < filepath.length() - 1)) {
return filepath.substring(sep + 1);
}
}
return filepath;
}
}

View File

@@ -0,0 +1,95 @@
package com.yizhuan.xchat_android_library.utils;
import java.math.RoundingMode;
import java.text.DecimalFormat;
/**
* <p> 格式化工具类 </p>
*
* @author jiahui
* @date 2018/1/9
*/
public class FormatUtils {
public static String formatBigDecimal(double bigDecimal) {
try {
DecimalFormat decimalFormat = new DecimalFormat("#,##0.00");
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
return decimalFormat.format(bigDecimal);
} catch (Exception e) {
}
return "0.00";
}
/**
* 把过长的金额类数字,转换成两位小数带万,亿,兆 缩写
* 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.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) + "";
}else if(Math.abs(num / yi) >= 1 && Math.abs(num /zhao) < 1){
res = num / yi;
return decimalFormat.format(res) + "亿";
}else {
res = num / zhao;
return decimalFormat.format(res) + "";
}
} catch (Exception e) {
return "格式化异常。";
}
}
/**
* 将手机号码 显示成带隐私形式
* @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();
}
}

View File

@@ -0,0 +1,27 @@
package com.yizhuan.xchat_android_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();
}
}
}

View File

@@ -0,0 +1,246 @@
package com.yizhuan.xchat_android_library.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class ImageUtils {
public static Bitmap path2Bitmap(String path, int w, int h){
BitmapFactory.Options opts = new BitmapFactory.Options();
// 设置为ture只获取图片大小
opts.inJustDecodeBounds = true;
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
// 返回为空
BitmapFactory.decodeFile(path, opts);
int width = opts.outWidth;
int height = opts.outHeight;
float scaleWidth = 0.f, scaleHeight = 0.f;
if (width > w || height > h) {
// 缩放
scaleWidth = ((float) width) / w;
scaleHeight = ((float) height) / h;
}
opts.inJustDecodeBounds = false;
float scale = Math.max(scaleWidth, scaleHeight);
opts.inSampleSize = (int)scale;
WeakReference<Bitmap> weak = new WeakReference<Bitmap>(BitmapFactory.decodeFile(path, opts));
return Bitmap.createScaledBitmap(weak.get(), w, h, true);
}
public static Bitmap ImageView2Bitmap(ImageView iv){
if(null != iv && null != iv.getDrawable()){
Bitmap image = ((BitmapDrawable)iv.getDrawable()).getBitmap();
return image;
}else{
return null;
}
}
public static void recycle(ImageView iv) {
if(null != iv){
Bitmap pBitmap = ImageView2Bitmap(iv);
if(null != pBitmap){
SoftReference<Bitmap> bitmap;
bitmap = new SoftReference<Bitmap>(pBitmap);
if (bitmap != null) {
if (bitmap.get() != null && !bitmap.get().isRecycled()) {
// bitmap.get().recycle();
bitmap = null;
}
}
}
}
}
// 等比例缩放图片
public static Bitmap zoomImg(String imgPath, int newWidth, int newHeight) {
// 图片源
Bitmap bm = BitmapFactory.decodeFile(imgPath);
if (null != bm) {
return zoomImg(bm, newWidth, newHeight);
}
return null;
}
public static Bitmap zoomImg(Context context, String img, int newWidth, int newHeight) {
// 图片源
try {
Bitmap bm = BitmapFactory.decodeStream(context.getAssets().open(img));
if (null != bm) {
return zoomImg(bm, newWidth, newHeight);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
// 缩放图片
public static Bitmap zoomImg(Bitmap bm, int newWidth, int newHeight) {
// 获得图片的宽高
int width = bm.getWidth();
int height = bm.getHeight();
// 计算缩放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的图片
Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
bm = null;
return newbm;
}
/**
* 根据图片的url路径获得Bitmap对象
*
* @param url
* @return
*/
public static Bitmap urlToBitmap(String url) {
URL fileUrl = null;
Bitmap bitmap = null;
try {
fileUrl = new URL(url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
HttpURLConnection conn = (HttpURLConnection) fileUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
/**
* @param urlpath
* @return Bitmap 根据图片url获取图片对象
*/
public static Bitmap getBitMBitmap(String urlpath) {
Bitmap map = null;
try {
URL url = new URL(urlpath);
URLConnection conn = url.openConnection();
conn.connect();
InputStream in;
in = conn.getInputStream();
map = BitmapFactory.decodeStream(in);
// TODO Auto-generated catch block
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
/**
* @param urlpath
* @return Bitmap 根据url获取布局背景的对<E79A84>?
*/
public static Drawable getDrawable(String urlpath) {
Drawable d = null;
try {
URL url = new URL(urlpath);
URLConnection conn = url.openConnection();
conn.connect();
InputStream in;
in = conn.getInputStream();
d = Drawable.createFromStream(in, "background.jpg");
// TODO Auto-generated catch block
} catch (IOException e) {
e.printStackTrace();
}
return d;
}
/**
* 读取图片的旋转的角度
*
* @param path
* 图片绝对路径
* @return 图片的旋转角度
*/
private int getBitmapDegree(String path) {
int degree = 0;
try {
// 从指定路径下读取图片并获取其EXIF信息
ExifInterface exifInterface = new ExifInterface(path);
// 获取图片的旋转信息
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
/**
* 将图片按照某个角度进行旋转
*
* @param bm
* 需要旋转的图片
* @param degree
* 旋转角度
* @return 旋转后的图片
*/
public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {
Bitmap returnBm = null;
// 根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
}
if (returnBm == null) {
returnBm = bm;
}
if (bm != returnBm) {
bm.recycle();
}
return returnBm;
}
}

View File

@@ -0,0 +1,63 @@
package com.yizhuan.xchat_android_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.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,248 @@
package com.yizhuan.xchat_android_library.utils;
import android.annotation.SuppressLint;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
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.util.List;
/**
* 数据类型转换
* @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","parseLong错误");
}
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","parseInt错误");
}
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","str2double错误");
}
return result;
}
return 0.0;
}
public static String str2double2len(String str){
try {
DecimalFormat df = new DecimalFormat("######0.00");
String format = df.format(str2double(str));
return format;
} catch (Exception e) {
Log.e("JavaUtil","str2double2len错误");
}
return null;
}
public static String str2double0len(String str){
try {
DecimalFormat df = new DecimalFormat("######0");
String format = df.format(str2double(str));
return format;
} catch (Exception e) {
Log.e("JavaUtil","str2double0len错误");
}
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","parseFloat错误");
}
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.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("")) {
i = 1;
} else if (str.equals("")) {
i = 2;
} else if (str.equals("")) {
i = 3;
} else if (str.equals("")) {
i = 4;
} else if (str.equals("")) {
i = 5;
} else if (str.equals("")) {
i = 6;
} else if (str.equals("") || str.equals("")) {
i = 7;
}
return i;
}
}

View File

@@ -0,0 +1,14 @@
package com.yizhuan.xchat_android_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);
}
}

View File

@@ -0,0 +1,46 @@
package com.yizhuan.xchat_android_library.utils;
import com.yizhuan.xchat_android_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;
}
}
}

View File

@@ -0,0 +1,18 @@
package com.yizhuan.xchat_android_library.utils;
import android.util.Log;
import com.yizhuan.xchat_android_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);
}
}
}

View File

@@ -0,0 +1,379 @@
package com.yizhuan.xchat_android_library.utils;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.yizhuan.xchat_android_library.BuildConfig;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* Created by MadisonRong on 2019/7/5
*/
public class MacAddressUtils {
public static String getMac(Context context) {
String strMac = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (BuildConfig.DEBUG) {
Log.e("=====", "6.0以下");
// Toast.makeText(context, "6.0以下", Toast.LENGTH_SHORT).show();
SingleToastUtil.showToastShort("6.0以下");
}
strMac = getLocalMacAddressFromWifiInfo(context);
return strMac;
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (BuildConfig.DEBUG) {
Log.e("=====", "6.0以上7.0以下");
// Toast.makeText(context, "6.0以上7.0以下", Toast.LENGTH_SHORT).show();
SingleToastUtil.showToastShort("6.0以上7.0以下");
}
strMac = getMacAddress(context);
return strMac;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.e("=====", "7.0以上");
if (!TextUtils.isEmpty(getMacAddress())) {
if (BuildConfig.DEBUG) {
Log.e("=====", "7.0以上1");
// Toast.makeText(context, "7.0以上1", Toast.LENGTH_SHORT).show();
SingleToastUtil.showToastShort("7.0以上1");
}
strMac = getMacAddress();
return strMac;
} else if (!TextUtils.isEmpty(getMachineHardwareAddress())) {
if (BuildConfig.DEBUG) {
Log.e("=====", "7.0以上2");
// Toast.makeText(context, "7.0以上2", Toast.LENGTH_SHORT).show();
SingleToastUtil.showToastShort("7.0以上2");
}
strMac = getMachineHardwareAddress();
return strMac;
} else {
if (BuildConfig.DEBUG) {
Log.e("=====", "7.0以上3");
// Toast.makeText(context, "7.0以上3", Toast.LENGTH_SHORT).show();
SingleToastUtil.showToastShort("7.0以上3");
}
strMac = getLocalMacAddressFromBusybox();
return strMac;
}
}
return "02:00:00:00:00:00";
}
/**
* 根据wifi信息获取本地mac
* @param context
* @return
*/
public static String getLocalMacAddressFromWifiInfo(Context context) {
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo winfo = wifi.getConnectionInfo();
String mac = winfo.getMacAddress();
return mac;
}
/**
* android 6.0及以上、7.0以下 获取mac地址
*
* @param context
* @return
*/
public static String getMacAddress(Context context) {
// 如果是6.0以下直接通过wifimanager获取
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
String macAddress0 = getMacAddress0(context);
if (!TextUtils.isEmpty(macAddress0)) {
return macAddress0;
}
}
String str = "";
String macSerial = "";
try {
Process pp = Runtime.getRuntime().exec(
"cat /sys/class/net/wlan0/address");
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
for (; null != str; ) {
str = input.readLine();
if (str != null) {
macSerial = str.trim();// 去空格
break;
}
}
} catch (Exception ex) {
Log.e("----->" + "NetInfoManager", "getMacAddress:" + ex.toString());
}
if (macSerial == null || "".equals(macSerial)) {
try {
return loadFileAsString("/sys/class/net/eth0/address")
.toUpperCase().substring(0, 17);
} catch (Exception e) {
e.printStackTrace();
Log.e("----->" + "NetInfoManager",
"getMacAddress:" + e.toString());
}
}
return macSerial;
}
private static String getMacAddress0(Context context) {
if (isAccessWifiStateAuthorized(context)) {
WifiManager wifiMgr = (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = null;
try {
wifiInfo = wifiMgr.getConnectionInfo();
return wifiInfo.getMacAddress();
} catch (Exception e) {
Log.e("----->" + "NetInfoManager",
"getMacAddress0:" + e.toString());
}
}
return "";
}
/**
* Check whether accessing wifi state is permitted
*
* @param context
* @return
*/
private static boolean isAccessWifiStateAuthorized(Context context) {
if (PackageManager.PERMISSION_GRANTED == context
.checkCallingOrSelfPermission("android.permission.ACCESS_WIFI_STATE")) {
Log.e("----->" + "NetInfoManager", "isAccessWifiStateAuthorized:"
+ "access wifi state is enabled");
return true;
} else
return false;
}
private static String loadFileAsString(String fileName) throws Exception {
FileReader reader = new FileReader(fileName);
String text = loadReaderAsString(reader);
reader.close();
return text;
}
private static String loadReaderAsString(Reader reader) throws Exception {
StringBuilder builder = new StringBuilder();
char[] buffer = new char[4096];
int readLength = reader.read(buffer);
while (readLength >= 0) {
builder.append(buffer, 0, readLength);
readLength = reader.read(buffer);
}
return builder.toString();
}
/**
* 根据IP地址获取MAC地址
*
* @return
*/
public static String getMacAddress() {
String strMacAddr = null;
try {
// 获得IpD地址
InetAddress ip = getLocalInetAddress();
byte[] b = NetworkInterface.getByInetAddress(ip)
.getHardwareAddress();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < b.length; i++) {
if (i != 0) {
buffer.append(':');
}
String str = Integer.toHexString(b[i] & 0xFF);
buffer.append(str.length() == 1 ? 0 + str : str);
}
strMacAddr = buffer.toString().toUpperCase();
} catch (Exception e) {
}
return strMacAddr;
}
/**
* 获取移动设备本地IP
*
* @return
*/
private static InetAddress getLocalInetAddress() {
InetAddress ip = null;
try {
// 列举
Enumeration<NetworkInterface> en_netInterface = NetworkInterface
.getNetworkInterfaces();
while (en_netInterface.hasMoreElements()) {// 是否还有元素
NetworkInterface ni = (NetworkInterface) en_netInterface
.nextElement();// 得到下一个元素
Enumeration<InetAddress> en_ip = ni.getInetAddresses();// 得到一个ip地址的列举
while (en_ip.hasMoreElements()) {
ip = en_ip.nextElement();
if (!ip.isLoopbackAddress()
&& ip.getHostAddress().indexOf(":") == -1)
break;
else
ip = null;
}
if (ip != null) {
break;
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return ip;
}
/**
* 获取本地IP
*
* @return
*/
private static String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf
.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
ex.printStackTrace();
}
return null;
}
/**
* android 7.0及以上 2扫描各个网络接口获取mac地址
*
*/
/**
* 获取设备HardwareAddress地址
*
* @return
*/
public static String getMachineHardwareAddress() {
Enumeration<NetworkInterface> interfaces = null;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
String hardWareAddress = null;
NetworkInterface iF = null;
if (interfaces == null) {
return null;
}
while (interfaces.hasMoreElements()) {
iF = interfaces.nextElement();
try {
hardWareAddress = bytesToString(iF.getHardwareAddress());
if (hardWareAddress != null)
break;
} catch (SocketException e) {
e.printStackTrace();
}
}
return hardWareAddress;
}
/***
* byte转为String
*
* @param bytes
* @return
*/
private static String bytesToString(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
StringBuilder buf = new StringBuilder();
for (byte b : bytes) {
buf.append(String.format("%02X:", b));
}
if (buf.length() > 0) {
buf.deleteCharAt(buf.length() - 1);
}
return buf.toString();
}
/**
* android 7.0及以上 3通过busybox获取本地存储的mac地址
*
*/
/**
* 根据busybox获取本地Mac
*
* @return
*/
public static String getLocalMacAddressFromBusybox() {
String result = "";
String Mac = "";
result = callCmd("busybox ifconfig", "HWaddr");
// 如果返回的result == null则说明网络不可取
if (result == null) {
return "网络异常";
}
// 对该行数据进行解析
// 例如eth0 Link encap:Ethernet HWaddr 00:16:E8:3E:DF:67
if (result.length() > 0 && result.contains("HWaddr") == true) {
Mac = result.substring(result.indexOf("HWaddr") + 6,
result.length() - 1);
result = Mac;
}
return result;
}
private static String callCmd(String cmd, String filter) {
String result = "";
String line = "";
try {
Process proc = Runtime.getRuntime().exec(cmd);
InputStreamReader is = new InputStreamReader(proc.getInputStream());
BufferedReader br = new BufferedReader(is);
while ((line = br.readLine()) != null
&& line.contains(filter) == false) {
result += line;
}
result = line;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}

View File

@@ -0,0 +1,9 @@
package com.yizhuan.xchat_android_library.utils;
/**
* Created by lijun on 2015/7/20.
*/
public class MimeType {
public static final String APK = "application/vnd.android.package-archive";
}

View File

@@ -0,0 +1,509 @@
package com.yizhuan.xchat_android_library.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.List;
/**
* Created by xujiexing on 14-6-10.
*/
public class NetworkUtils {
private static WifiManager.WifiLock sWifiLocker;
private static final String TAG = NetworkUtils.class.toString();
static synchronized WifiManager.WifiLock wifiLocker(Context c) {
if (sWifiLocker == null) {
Log.d(TAG, "Create WifiManager for " + (Build.VERSION.SDK_INT >= 9 ? "WIFI_MODE_HIPREF" : "WIFI_MODE_FULL"));
sWifiLocker = ((WifiManager) c.getSystemService(Context.WIFI_SERVICE))
.createWifiLock(Build.VERSION.SDK_INT >= 9 ? 3 : WifiManager.WIFI_MODE_FULL, "YY");
}
return sWifiLocker;
}
public static void lockWifi(Context c) {
Log.d(TAG, "lock wifi");
if (!wifiLocker(c).isHeld())
wifiLocker(c).acquire();
}
public static void unlockWifi(Context c) {
Log.d(TAG, "unlock wifi");
if (wifiLocker(c).isHeld())
wifiLocker(c).release();
}
public static boolean isWifiActive(Context c) {
if(c == null){
Log.e("xuwakao", "isWifiActive is NULL");
return false;
}
ConnectivityManager mgr = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = mgr.getActiveNetworkInfo();
return networkInfo != null
&& networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
}
public static boolean isMobileActive(Context c) {
if(c == null){
Log.e("xuwakao", "isWifiActive is NULL");
return false;
}
ConnectivityManager mgr = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = mgr.getActiveNetworkInfo();
return networkInfo != null
&& networkInfo.getType() == ConnectivityManager.TYPE_MOBILE;
}
public static boolean isNetworkStrictlyAvailable(Context c) {
if(c == null){
Log.e("xuwakao", "isNetworkStrictlyAvailable context is NULL");
return false;
}
ConnectivityManager connectivityManager = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager == null) {
Log.e("xuwakao", "isNetworkStrictlyAvailable connectivityManager is NULL");
return false;
}
NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
if (ni != null && ni.isAvailable() && ni.isConnected()) {
return true;
} else {
String info = null;
if (ni != null) {
info = "network type = " + ni.getType() + ", "
+ (ni.isAvailable() ? "available" : "inavailable")
+ ", " + (ni.isConnected() ? "" : "not") + " connected";
} else {
info = "no active network";
}
Log.i("network", info);
return false;
}
}
public static boolean isNetworkAvailable(Context c) {
if (null == c) {
return false;
}
ConnectivityManager connectivityManager = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
if (ni == null) {
return false;
}
return ni.isConnected()
|| (ni.isAvailable() && ni.isConnectedOrConnecting());
}
// public static void showNetworkConfigDialog(final Context c, int msgId, int posStrId, int negStrId) {
// final AlertDialog dialog = new AlertDialog.Builder(c).create();
//
// dialog.show();
// Window window = dialog.getWindow();
// window.setContentView(R.layout.layout_network_error_dialog);
//
// TextView tip = (TextView)window.findViewById(R.id.message);
// tip.setText(c.getString(msgId));
//
// TextView ok = (TextView) window.findViewById(R.id.btn_ok);
// ok.setText(c.getString(posStrId));
// ok.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// dialog.dismiss();
// openNetworkConfig(c);
// }
// });
//
// TextView cancel = (TextView) window.findViewById(R.id.btn_cancel);
// cancel.setText(c.getString(negStrId));
// cancel.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// dialog.dismiss();
// }
// });
// }
//
// public static void showNetworkConfigDialog(Context c) {
// NetworkUtils.showNetworkConfigDialog(c,
// R.string.network_error, R.string.set_network,
// R.string.cancel);
// }
public static void openNetworkConfig(Context c) {
Intent i = null;
if (Build.VERSION.SDK_INT > 10) {
i = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
} else {
i = new Intent();
i.setClassName("com.android.settings", "com.android.settings.WirelessSettings");
i.setAction(Intent.ACTION_MAIN);
}
try {
c.startActivity(i);
} catch (Exception e) {
}
}
@SuppressLint("MissingPermission")
public static String getSubscriberId(Context c) {
TelephonyManager tm = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE);
String id = null;
if (tm != null) {
id = tm.getSubscriberId();
}
return id != null ? id : "";
}
private static final int MIN_PORT = 0;
private static final int MAX_PORT = 65535;
private static final int DEFAULT_PROXY_PORT = 80;
public static InetSocketAddress getTunnelProxy(Context c) {
if (c.checkCallingOrSelfPermission("android.permission.WRITE_APN_SETTINGS") ==
PackageManager.PERMISSION_DENIED) {
return null;
}
ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null) {
if (netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return null;
}
}
String proxy = "";
String portStr = "";
Uri uri = Uri.parse("content://telephony/carriers/preferapn");
Cursor cr = c.getContentResolver().query(uri, null, null, null, null);
if (cr != null && cr.moveToNext()) {
proxy = cr.getString(cr.getColumnIndex("proxy"));
portStr = cr.getString(cr.getColumnIndex("port"));
Log.i("getTunnelProxy", TelephonyUtils.getOperator(c) + ", proxy = " + proxy + ", port = " + portStr);
if (proxy != null && proxy.length() > 0) {
cr.close();
cr = null;
int port;
try {
port = Integer.parseInt(portStr);
if (port < MIN_PORT || port > MAX_PORT) {
port = DEFAULT_PROXY_PORT;
}
} catch (Exception e) {
Log.i("getTunnelProxy", "port is invalid, e = " + e);
port = DEFAULT_PROXY_PORT;
}
InetSocketAddress addr = null;
try {
addr = new InetSocketAddress(proxy, port);
} catch (Exception e) {
Log.i("getTunnelProxy", "create address failed, e = " + e);
}
return addr;
}
}
if (cr != null) {
cr.close();
cr = null;
}
return null;
}
public static byte[] getIPArray(int ip) {
byte[] ipAddr = new byte[4];
ipAddr[0] = (byte) ip;
ipAddr[1] = (byte) (ip >>> 8);
ipAddr[2] = (byte) (ip >>> 16);
ipAddr[3] = (byte) (ip >>> 24);
return ipAddr;
}
public static String getIpString(byte[] ip) {
StringBuilder sb = new StringBuilder();
sb.append(ip[0] & 0xff);
sb.append(".");
sb.append(ip[1] & 0xff);
sb.append(".");
sb.append(ip[2] & 0xff);
sb.append(".");
sb.append(ip[3] & 0xff);
return sb.toString();
}
public static String getIpString(int ip) {
StringBuilder sb = new StringBuilder();
sb.append(ip & 0xff);
sb.append(".");
sb.append(ip >>> 8 & 0xff);
sb.append(".");
sb.append(ip >>> 16 & 0xff);
sb.append(".");
sb.append(ip >>> 24 & 0xff);
return sb.toString();
}
public static int getPort(List<Integer> ports) {
java.util.Random random = new java.util.Random(
System.currentTimeMillis());
return ports.get(random.nextInt(ports.size()));
}
public static int getLittleEndianInt(byte[] buffer, int start) {
int i = buffer[start + 0] & 0xff;
i |= (buffer[start + 1] << 8) & 0xff00;
i |= (buffer[start + 2] << 16) & 0xff0000;
i |= (buffer[start + 3] << 24) & 0xff000000;
return i;
}
public static byte[] toBytes(ByteBuffer buffer) {
if (buffer == null) {
return new byte[0];
}
int savedPos = buffer.position();
int savedLimit = buffer.limit();
try {
byte[] array = new byte[buffer.limit() - buffer.position()];
if (buffer.hasArray()) {
int offset = buffer.arrayOffset() + savedPos;
byte[] bufferArray = buffer.array();
System.arraycopy(bufferArray, offset, array, 0, array.length);
return array;
} else {
buffer.get(array);
return array;
}
} finally {
buffer.position(savedPos);
buffer.limit(savedLimit);
}
}
public static String getNetwrokNameByType(Context c, int type) {
ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo[] infos = cm.getAllNetworkInfo();
for (NetworkInfo network : infos) {
if (network.getType() == type) {
return network.getTypeName();
}
}
return "Unknown";
}
public static final int NET_INVALID = 0 ; // 无网络
public static final int NET_WIFI = 1;
public static final int NET_2G = 2;
public static final int NET_3G = 3;
public static final int NET_LEGACY = 4; // legacy client
public static final int UNKNOW_NETWORK_TYPE = 5;
public static NetworkInfo getActiveNetwork(Context c) {
ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
return cm.getActiveNetworkInfo();
} catch (Exception e) {
Log.e("NetworkUtils", "error on getActiveNetworkInfo " + e.toString());
}
return null;
}
/**
* get the machine address of wifi
*
* @param c
* @return
*/
public static String getWifiMacAddr(Context c) {
if (c != null) {
WifiManager wifiMan = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInf = wifiMan.getConnectionInfo();
if (wifiInf != null) {
return wifiInf.getMacAddress();
}
}
return "";
}
/**
* get the type of network
*
* @param c
* @return
*/
public static int getNetworkType(Context c) {
int networkType = UNKNOW_NETWORK_TYPE;
NetworkInfo netInfo = getActiveNetwork(c);
if (netInfo != null) {
int type = netInfo.getType();
if (type == ConnectivityManager.TYPE_WIFI
|| type == ConnectivityManager.TYPE_WIMAX) {
networkType = NET_WIFI;
} else if (type == ConnectivityManager.TYPE_MOBILE) {
int subType = netInfo.getSubtype();
if (subType == TelephonyManager.NETWORK_TYPE_1xRTT
|| subType == TelephonyManager.NETWORK_TYPE_UMTS
|| subType == TelephonyManager.NETWORK_TYPE_EHRPD
|| subType == TelephonyManager.NETWORK_TYPE_EVDO_0
|| subType == TelephonyManager.NETWORK_TYPE_EVDO_A
|| subType == TelephonyManager.NETWORK_TYPE_EVDO_B
|| subType == TelephonyManager.NETWORK_TYPE_HSDPA
|| subType == TelephonyManager.NETWORK_TYPE_HSPA
|| subType == TelephonyManager.NETWORK_TYPE_HSPAP
|| subType == TelephonyManager.NETWORK_TYPE_HSUPA
|| subType == TelephonyManager.NETWORK_TYPE_LTE) {
networkType = NET_3G;
} else if (subType == TelephonyManager.NETWORK_TYPE_GPRS
|| subType == TelephonyManager.NETWORK_TYPE_CDMA
|| subType == TelephonyManager.NETWORK_TYPE_EDGE
|| subType == TelephonyManager.NETWORK_TYPE_IDEN) {
networkType = NET_2G;
}
}
}
return networkType;
}
public static String getNetworkTypeName(Context context) {
int type = getNetworkType(context);
switch (type) {
case NET_WIFI:
return "WI-FI";
case NET_2G:
return "2G";
case NET_3G:
return "3G";
case NET_INVALID:
case NET_LEGACY:
case UNKNOW_NETWORK_TYPE:
return "UNKNOWN";
default:
return "4G";
}
}
public static String bytesToHexString(byte[] bytes) {
if (bytes == null) {
return "";
}
StringBuffer sb = new StringBuffer();
for (byte b : bytes) {
int val = b & 0xff;
if (val < 0x10) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString();
}
public static String getSimOperator(Context c) {
TelephonyManager tm = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE);
return tm.getSimOperator();
}
public static String getOperator(Context c) {
String sim = getSimOperator(c);
if(FP.empty(sim))
return ChinaOperator.UNKNOWN;
if (sim.startsWith("46003") || sim.startsWith("46005")) {
return ChinaOperator.CTL;
} else if (sim.startsWith("46001") || sim.startsWith("46006")) {
return ChinaOperator.UNICOM;
} else if (sim.startsWith("46000") || sim.startsWith("46002")
|| sim.startsWith("46007") || sim.startsWith("46020")){
return ChinaOperator.CMCC;
}
else {
return ChinaOperator.UNKNOWN;
}
}
public static class ChinaOperator {
public static final String CMCC = "CMCC";
public static final String CTL = "CTL";
public static final String UNICOM = "UNICOM";
public static final String UNKNOWN = "Unknown";
}
public static String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
Log.e("NetworkUtils getLocalIpAddress:", ex.toString());
}
return null;
}
public static String getIPAddress(Context c) {
NetworkInfo info = ((ConnectivityManager) c
.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (info != null && info.isConnected()) {
if (info.getType() == ConnectivityManager.TYPE_MOBILE) {//当前使用2G/3G/4G网络
try {
//Enumeration<NetworkInterface> en=NetworkInterface.getNetworkInterfaces();
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
return inetAddress.getHostAddress();
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
} else if (info.getType() == ConnectivityManager.TYPE_WIFI) {//当前使用无线网络
WifiManager wifiManager = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String ipAddress = intIP2StringIP(wifiInfo.getIpAddress());//得到IPV4地址
return ipAddress;
}
} else {
//当前无网络连接,请在设置中打开网络
}
return null;
}
/**
* 将得到的int类型的IP转换为String类型
*
* @param ip
* @return
*/
public static String intIP2StringIP(int ip) {
return (ip & 0xFF) + "." +
((ip >> 8) & 0xFF) + "." +
((ip >> 16) & 0xFF) + "." +
(ip >> 24 & 0xFF);
}
}

View File

@@ -0,0 +1,21 @@
package com.yizhuan.xchat_android_library.utils;
/**
* <p> </p>
*
* @author jiahui
* @date 2017/12/4
*/
public class NullUtils {
public static void checkNull(Object o) {
if (o == null) {
throw new IllegalArgumentException(o + " 不能为null !");
}
}
public static void checkNull(Object o, String errMsg) {
if (o == null) {
throw new IllegalArgumentException(errMsg);
}
}
}

View File

@@ -0,0 +1,837 @@
/*
* 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.yizhuan.xchat_android_library.utils;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
/**
* <p>Operations on {@code Object}.</p>
* <p/>
* <p>This class tries to handle {@code null} input gracefully.
* An exception will generally not be thrown for a {@code null} input.
* Each method documents its behaviour in more detail.</p>
* <p/>
* <p>#ThreadSafe#</p>
*
* @version $Id: ObjectUtils.java 1583781 2014-04-01 20:54:53Z niallp $
* @since 1.0
*/
//@Immutable
public class ObjectUtils {
/**
* <p>Singleton used as a {@code null} placeholder where
* {@code null} has another meaning.</p>
* <p/>
* <p>For example, in a {@code HashMap} the
* {@link java.util.HashMap#get(Object)} method returns
* {@code null} if the {@code Map} contains {@code null} or if there
* is no matching key. The {@code Null} placeholder can be used to
* distinguish between these two cases.</p>
* <p/>
* <p>Another example is {@code Hashtable}, where {@code null}
* cannot be stored.</p>
* <p/>
* <p>This instance is Serializable.</p>
*/
public static final Null NULL = new Null();
/**
* <p>{@code ObjectUtils} instances should NOT be constructed in
* standard programming. Instead, the static methods on the class should
* be used, such as {@code ObjectUtils.defaultIfNull("a","b");}.</p>
* <p/>
* <p>This constructor is public to permit tools that require a JavaBean
* instance to operate.</p>
*/
public ObjectUtils() {
super();
}
// Defaulting
//-----------------------------------------------------------------------
/**
* <p>Returns a default value if the object passed is {@code null}.</p>
* <p/>
* <pre>
* ObjectUtils.defaultIfNull(null, null) = null
* ObjectUtils.defaultIfNull(null, "") = ""
* ObjectUtils.defaultIfNull(null, "zz") = "zz"
* ObjectUtils.defaultIfNull("abc", *) = "abc"
* ObjectUtils.defaultIfNull(Boolean.TRUE, *) = Boolean.TRUE
* </pre>
*
* @param <T> the type of the object
* @param object the {@code Object} to test, may be {@code null}
* @param defaultValue the default value to return, may be {@code null}
* @return {@code object} if it is not {@code null}, defaultValue otherwise
*/
public static <T> T defaultIfNull(final T object, final T defaultValue) {
return object != null ? object : defaultValue;
}
/**
* <p>Returns the first value in the array which is not {@code null}.
* If all the values are {@code null} or the array is {@code null}
* or empty then {@code null} is returned.</p>
* <p/>
* <pre>
* ObjectUtils.firstNonNull(null, null) = null
* ObjectUtils.firstNonNull(null, "") = ""
* ObjectUtils.firstNonNull(null, null, "") = ""
* ObjectUtils.firstNonNull(null, "zz") = "zz"
* ObjectUtils.firstNonNull("abc", *) = "abc"
* ObjectUtils.firstNonNull(null, "xyz", *) = "xyz"
* ObjectUtils.firstNonNull(Boolean.TRUE, *) = Boolean.TRUE
* ObjectUtils.firstNonNull() = null
* </pre>
*
* @param <T> the component type of the array
* @param values the values to test, may be {@code null} or empty
* @return the first value from {@code values} which is not {@code null},
* or {@code null} if there are no non-null values
* @since 3.0
*/
public static <T> T firstNonNull(final T... values) {
if (values != null) {
for (final T val : values) {
if (val != null) {
return val;
}
}
}
return null;
}
// Null-safe equals/hashCode
//-----------------------------------------------------------------------
/**
* <p>Compares two objects for equality, where either one or both
* objects may be {@code null}.</p>
* <p/>
* <pre>
* ObjectUtils.equals(null, null) = true
* ObjectUtils.equals(null, "") = false
* ObjectUtils.equals("", null) = false
* ObjectUtils.equals("", "") = true
* ObjectUtils.equals(Boolean.TRUE, null) = false
* ObjectUtils.equals(Boolean.TRUE, "true") = false
* ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE) = true
* ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
* </pre>
*
* @param object1 the first object, may be {@code null}
* @param object2 the second object, may be {@code null}
* @return {@code true} if the values of both objects are the same
* @deprecated this method has been replaced by {@code java.util.Objects.equals(Object, Object)} in Java 7 and will
* be removed from future releases.
*/
@Deprecated
public static boolean objectEquals(final Object object1, final Object object2) {
if (object1 == object2) {
return true;
}
if (object1 == null || object2 == null) {
return false;
}
return object1.equals(object2);
}
/**
* <p>Compares two objects for inequality, where either one or both
* objects may be {@code null}.</p>
* <p/>
* <pre>
* ObjectUtils.notEqual(null, null) = false
* ObjectUtils.notEqual(null, "") = true
* ObjectUtils.notEqual("", null) = true
* ObjectUtils.notEqual("", "") = false
* ObjectUtils.notEqual(Boolean.TRUE, null) = true
* ObjectUtils.notEqual(Boolean.TRUE, "true") = true
* ObjectUtils.notEqual(Boolean.TRUE, Boolean.TRUE) = false
* ObjectUtils.notEqual(Boolean.TRUE, Boolean.FALSE) = true
* </pre>
*
* @param object1 the first object, may be {@code null}
* @param object2 the second object, may be {@code null}
* @return {@code false} if the values of both objects are the same
*/
public static boolean notEqual(final Object object1, final Object object2) {
return ObjectUtils.objectEquals(object1, object2) == false;
}
/**
* <p>Gets the hash code of an object returning zero when the
* object is {@code null}.</p>
* <p/>
* <pre>
* ObjectUtils.hashCode(null) = 0
* ObjectUtils.hashCode(obj) = obj.hashCode()
* </pre>
*
* @param obj the object to obtain the hash code of, may be {@code null}
* @return the hash code of the object, or zero if null
* @since 2.1
* @deprecated this method has been replaced by {@code java.util.Objects.hashCode(Object)} in Java 7 and will be
* removed in future releases
*/
@Deprecated
public static int hashCode(final Object obj) {
// hashCode(Object) retained for performance, as hash code is often critical
return obj == null ? 0 : obj.hashCode();
}
/**
* <p>Gets the hash code for multiple objects.</p>
* <p/>
* <p>This allows a hash code to be rapidly calculated for a number of objects.
* The hash code for a single object is the <em>not</em> same as {@link #hashCode(Object)}.
* The hash code for multiple objects is the same as that calculated by an
* {@code ArrayList} containing the specified objects.</p>
* <p/>
* <pre>
* ObjectUtils.hashCodeMulti() = 1
* ObjectUtils.hashCodeMulti((Object[]) null) = 1
* ObjectUtils.hashCodeMulti(a) = 31 + a.hashCode()
* ObjectUtils.hashCodeMulti(a,b) = (31 + a.hashCode()) * 31 + b.hashCode()
* ObjectUtils.hashCodeMulti(a,b,c) = ((31 + a.hashCode()) * 31 + b.hashCode()) * 31 + c.hashCode()
* </pre>
*
* @param objects the objects to obtain the hash code of, may be {@code null}
* @return the hash code of the objects, or zero if null
* @since 3.0
* @deprecated this method has been replaced by {@code java.util.Objects.hash(Object...)} in Java 7 an will be
* removed in future releases.
*/
@Deprecated
public static int hashCodeMulti(final Object... objects) {
int hash = 1;
if (objects != null) {
for (final Object object : objects) {
int tmpHash = ObjectUtils.hashCode(object);
hash = hash * 31 + tmpHash;
}
}
return hash;
}
/**
* <p>Gets the toString that would be produced by {@code Object}
* if a class did not override toString itself. {@code null}
* will return {@code null}.</p>
* <p/>
* <pre>
* ObjectUtils.identityToString(null) = null
* ObjectUtils.identityToString("") = "java.lang.String@1e23"
* ObjectUtils.identityToString(Boolean.TRUE) = "java.lang.Boolean@7fa"
* </pre>
*
* @param object the object to create a toString for, may be
* {@code null}
* @return the default toString text, or {@code null} if
* {@code null} passed in
*/
public static String identityToString(final Object object) {
if (object == null) {
return null;
}
final StringBuilder builder = new StringBuilder();
identityToString(builder, object);
return builder.toString();
}
// Identity ToString
//-----------------------------------------------------------------------
/**
* <p>Appends the toString that would be produced by {@code Object}
* if a class did not override toString itself. {@code null}
* will throw a NullPointerException for either of the two parameters. </p>
* <p/>
* <pre>
* ObjectUtils.identityToString(appendable, "") = appendable.append("java.lang.String@1e23"
* ObjectUtils.identityToString(appendable, Boolean.TRUE) = appendable.append("java.lang.Boolean@7fa"
* ObjectUtils.identityToString(appendable, Boolean.TRUE) = appendable.append("java.lang.Boolean@7fa")
* </pre>
*
* @param appendable the appendable to append to
* @param object the object to create a toString for
* @throws IOException if an I/O error occurs
* @since 3.2
*/
public static void identityToString(final Appendable appendable, final Object object) throws IOException {
if (object == null) {
throw new NullPointerException("Cannot get the toString of a null identity");
}
appendable.append(object.getClass().getName())
.append('@')
.append(Integer.toHexString(System.identityHashCode(object)));
}
/**
* <p>Appends the toString that would be produced by {@code Object}
* if a class did not override toString itself. {@code null}
* will throw a NullPointerException for either of the two parameters. </p>
* <p/>
* <pre>
* ObjectUtils.identityToString(buf, "") = buf.append("java.lang.String@1e23"
* ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa"
* ObjectUtils.identityToString(buf, Boolean.TRUE) = buf.append("java.lang.Boolean@7fa")
* </pre>
*
* @param buffer the buffer to append to
* @param object the object to create a toString for
* @since 2.4
*/
public static void identityToString(final StringBuffer buffer, final Object object) {
if (object == null) {
throw new NullPointerException("Cannot get the toString of a null identity");
}
buffer.append(object.getClass().getName())
.append('@')
.append(Integer.toHexString(System.identityHashCode(object)));
}
/**
* <p>Appends the toString that would be produced by {@code Object}
* if a class did not override toString itself. {@code null}
* will throw a NullPointerException for either of the two parameters. </p>
* <p/>
* <pre>
* ObjectUtils.identityToString(builder, "") = builder.append("java.lang.String@1e23"
* ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa"
* ObjectUtils.identityToString(builder, Boolean.TRUE) = builder.append("java.lang.Boolean@7fa")
* </pre>
*
* @param builder the builder to append to
* @param object the object to create a toString for
* @since 3.2
*/
public static void identityToString(final StringBuilder builder, final Object object) {
if (object == null) {
throw new NullPointerException("Cannot get the toString of a null identity");
}
builder.append(object.getClass().getName())
.append('@')
.append(Integer.toHexString(System.identityHashCode(object)));
}
/**
* <p>Gets the {@code toString} of an {@code Object} returning
* an empty string ("") if {@code null} input.</p>
* <p/>
* <pre>
* ObjectUtils.toString(null) = ""
* ObjectUtils.toString("") = ""
* ObjectUtils.toString("bat") = "bat"
* ObjectUtils.toString(Boolean.TRUE) = "true"
* </pre>
*
* @param obj the Object to {@code toString}, may be null
* @return the passed in Object's toString, or {@code ""} if {@code null} input
* @see String#valueOf(Object)
* @since 2.0
* @deprecated this method has been replaced by {@code java.util.Objects.toString(Object)} in Java 7 and will be
* removed in future releases. Note however that said method will return "null" for null references, while this
* method returns and empty String. To preserve behavior use {@code java.util.Objects.toString(myObject, "")}
*/
@Deprecated
public static String toString(final Object obj) {
return obj == null ? "" : obj.toString();
}
// ToString
//-----------------------------------------------------------------------
/**
* <p>Gets the {@code toString} of an {@code Object} returning
* a specified text if {@code null} input.</p>
* <p/>
* <pre>
* ObjectUtils.toString(null, null) = null
* ObjectUtils.toString(null, "null") = "null"
* ObjectUtils.toString("", "null") = ""
* ObjectUtils.toString("bat", "null") = "bat"
* ObjectUtils.toString(Boolean.TRUE, "null") = "true"
* </pre>
*
* @param obj the Object to {@code toString}, may be null
* @param nullStr the String to return if {@code null} input, may be null
* @return the passed in Object's toString, or {@code nullStr} if {@code null} input
* @see String#valueOf(Object)
* @since 2.0
* @deprecated this method has been replaced by {@code java.util.Objects.toString(Object, String)} in Java 7 and
* will be removed in future releases.
*/
@Deprecated
public static String toString(final Object obj, final String nullStr) {
return obj == null ? nullStr : obj.toString();
}
/**
* <p>Null safe comparison of Comparables.</p>
*
* @param <T> type of the values processed by this method
* @param values the set of comparable values, may be null
* @return <ul>
* <li>If any objects are non-null and unequal, the lesser object.
* <li>If all objects are non-null and equal, the first.
* <li>If any of the comparables are null, the lesser of the non-null objects.
* <li>If all the comparables are null, null is returned.
* </ul>
*/
public static <T extends Comparable<? super T>> T min(final T... values) {
T result = null;
if (values != null) {
for (final T value : values) {
if (compare(value, result, true) < 0) {
result = value;
}
}
}
return result;
}
// Comparable
//-----------------------------------------------------------------------
/**
* <p>Null safe comparison of Comparables.</p>
*
* @param <T> type of the values processed by this method
* @param values the set of comparable values, may be null
* @return <ul>
* <li>If any objects are non-null and unequal, the greater object.
* <li>If all objects are non-null and equal, the first.
* <li>If any of the comparables are null, the greater of the non-null objects.
* <li>If all the comparables are null, null is returned.
* </ul>
*/
public static <T extends Comparable<? super T>> T max(final T... values) {
T result = null;
if (values != null) {
for (final T value : values) {
if (compare(value, result, false) > 0) {
result = value;
}
}
}
return result;
}
/**
* <p>Null safe comparison of Comparables.
* {@code null} is assumed to be less than a non-{@code null} value.</p>
*
* @param <T> type of the values processed by this method
* @param c1 the first comparable, may be null
* @param c2 the second comparable, may be null
* @return a negative value if c1 &lt; c2, zero if c1 = c2
* and a positive value if c1 &gt; c2
*/
public static <T extends Comparable<? super T>> int compare(final T c1, final T c2) {
return compare(c1, c2, false);
}
/**
* <p>Null safe comparison of Comparables.</p>
*
* @param <T> type of the values processed by this method
* @param c1 the first comparable, may be null
* @param c2 the second comparable, may be null
* @param nullGreater if true {@code null} is considered greater
* than a non-{@code null} value or if false {@code null} is
* considered less than a Non-{@code null} value
* @return a negative value if c1 &lt; c2, zero if c1 = c2
* and a positive value if c1 &gt; c2
* @see Comparator#compare(Object, Object)
*/
public static <T extends Comparable<? super T>> int compare(final T c1, final T c2, final boolean nullGreater) {
if (c1 == c2) {
return 0;
} else if (c1 == null) {
return nullGreater ? 1 : -1;
} else if (c2 == null) {
return nullGreater ? -1 : 1;
}
return c1.compareTo(c2);
}
/**
* Find the "best guess" middle value among comparables. If there is an even
* number of total values, the lower of the two middle values will be returned.
*
* @param <T> type of values processed by this method
* @param items to compare
* @return T at middle position
* @throws NullPointerException if items is {@code null}
* @throws IllegalArgumentException if items is empty or contains {@code null} values
* @since 3.0.1
*/
public static <T extends Comparable<? super T>> T median(final T... items) {
Validate.notEmpty(items);
Validate.noNullElements(items);
final TreeSet<T> sort = new TreeSet<T>();
Collections.addAll(sort, items);
@SuppressWarnings("unchecked") //we know all items added were T instances
final
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
return result;
}
/**
* Find the "best guess" middle value among comparables. If there is an even
* number of total values, the lower of the two middle values will be returned.
*
* @param <T> type of values processed by this method
* @param comparator to use for comparisons
* @param items to compare
* @return T at middle position
* @throws NullPointerException if items or comparator is {@code null}
* @throws IllegalArgumentException if items is empty or contains {@code null} values
* @since 3.0.1
*/
public static <T> T median(final Comparator<T> comparator, final T... items) {
Validate.notEmpty(items, "null/empty items");
Validate.noNullElements(items);
Validate.notNull(comparator, "null comparator");
final TreeSet<T> sort = new TreeSet<T>(comparator);
Collections.addAll(sort, items);
@SuppressWarnings("unchecked") //we know all items added were T instances
final
T result = (T) sort.toArray()[(sort.size() - 1) / 2];
return result;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static boolean MAGIC_FLAG = ObjectUtils.CONST(true);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the boolean value to return
* @return the boolean v, unchanged
* @since 3.2
*/
public static boolean CONST(final boolean v) {
return v;
}
// Null
//-----------------------------------------------------------------------
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static byte MAGIC_BYTE = ObjectUtils.CONST((byte) 127);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the byte value to return
* @return the byte v, unchanged
* @since 3.2
*/
public static byte CONST(final byte v) {
return v;
}
// Constants (LANG-816):
/*
These methods ensure constants are not inlined by javac.
For example, typically a developer might declare a constant like so:
public final static int MAGIC_NUMBER = 5;
Should a different jar file refer to this, and the MAGIC_NUMBER
is changed a later date (e.g., MAGIC_NUMBER = 6), the different jar
file will need to recompile itself. This is because javac
typically inlines the primitive or String constant directly into
the bytecode, and removes the reference to the MAGIC_NUMBER field.
To help the other jar (so that it does not need to recompile
when constants are changed) the original developer can declare
their constant using one of the CONST() utility methods, instead:
public final static int MAGIC_NUMBER = CONST(5);
*/
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static byte MAGIC_BYTE = ObjectUtils.CONST_BYTE(127);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the byte literal (as an int) value to return
* @return the byte v, unchanged
* @throws IllegalArgumentException if the value passed to v
* is larger than a byte, that is, smaller than -128 or
* larger than 127.
* @since 3.2
*/
public static byte CONST_BYTE(final int v) throws IllegalArgumentException {
if (v < Byte.MIN_VALUE || v > Byte.MAX_VALUE) {
throw new IllegalArgumentException("Supplied value must be a valid byte literal between -128 and 127: [" + v + "]");
}
return (byte) v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static char MAGIC_CHAR = ObjectUtils.CONST('a');
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the char value to return
* @return the char v, unchanged
* @since 3.2
*/
public static char CONST(final char v) {
return v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static short MAGIC_SHORT = ObjectUtils.CONST((short) 123);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the short value to return
* @return the short v, unchanged
* @since 3.2
*/
public static short CONST(final short v) {
return v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static short MAGIC_SHORT = ObjectUtils.CONST_SHORT(127);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the short literal (as an int) value to return
* @return the byte v, unchanged
* @throws IllegalArgumentException if the value passed to v
* is larger than a short, that is, smaller than -32768 or
* larger than 32767.
* @since 3.2
*/
public static short CONST_SHORT(final int v) throws IllegalArgumentException {
if (v < Short.MIN_VALUE || v > Short.MAX_VALUE) {
throw new IllegalArgumentException("Supplied value must be a valid byte literal between -32768 and 32767: [" + v + "]");
}
return (short) v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static int MAGIC_INT = ObjectUtils.CONST(123);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the int value to return
* @return the int v, unchanged
* @since 3.2
*/
public static int CONST(final int v) {
return v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static long MAGIC_LONG = ObjectUtils.CONST(123L);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the long value to return
* @return the long v, unchanged
* @since 3.2
*/
public static long CONST(final long v) {
return v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static float MAGIC_FLOAT = ObjectUtils.CONST(1.0f);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the float value to return
* @return the float v, unchanged
* @since 3.2
*/
public static float CONST(final float v) {
return v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static double MAGIC_DOUBLE = ObjectUtils.CONST(1.0);
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param v the double value to return
* @return the double v, unchanged
* @since 3.2
*/
public static double CONST(final double v) {
return v;
}
/**
* This method returns the provided value unchanged.
* This can prevent javac from inlining a constant
* field, e.g.,
* <p/>
* <pre>
* public final static String MAGIC_STRING = ObjectUtils.CONST("abc");
* </pre>
* <p/>
* This way any jars that refer to this field do not
* have to recompile themselves if the field's value
* changes at some future date.
*
* @param <T> the Object type
* @param v the genericized Object value to return (typically a String).
* @return the genericized Object v, unchanged (typically a String).
* @since 3.2
*/
public static <T> T CONST(final T v) {
return v;
}
@Override
public String toString() {
return super.toString();
}
/**
* <p>Class used as a null placeholder where {@code null}
* has another meaning.</p>
* <p/>
* <p>For example, in a {@code HashMap} the
* {@link java.util.HashMap#get(Object)} method returns
* {@code null} if the {@code Map} contains {@code null} or if there is
* no matching key. The {@code Null} placeholder can be used to distinguish
* between these two cases.</p>
* <p/>
* <p>Another example is {@code Hashtable}, where {@code null}
* cannot be stored.</p>
*/
public static class Null implements Serializable {
/**
* Required for serialization support. Declare serialization compatibility with Commons Lang 1.0
*
* @see Serializable
*/
private static final long serialVersionUID = 7092611880189329093L;
/**
* Restricted constructor - singleton.
*/
Null() {
super();
}
/**
* <p>Ensure singleton.</p>
*
* @return the singleton value
*/
private Object readResolve() {
return ObjectUtils.NULL;
}
}
}

View File

@@ -0,0 +1,55 @@
package com.yizhuan.xchat_android_library.utils;
import android.os.Handler;
import android.os.Message;
/**
* Created by lijun on 2015/1/29.
*/
public class OperationTimer extends Handler {
private static final long SHORT_TIME = 600;
public static final int CLICK_MESSAGE = 100;
private Callback mCallback;
private long mShortTime = SHORT_TIME;
public OperationTimer(long mShortTime, Callback callback) {
this(callback);
this.mShortTime = mShortTime;
}
public OperationTimer(Callback callback) {
this.mCallback = callback;
}
public void setShortTime(long shortTime) {
this.mShortTime = shortTime;
}
public void notifyClick() {
removeMessages(CLICK_MESSAGE);
sendEmptyMessageDelayed(CLICK_MESSAGE, mShortTime);
}
public void stopNotifyClick() {
removeMessages(CLICK_MESSAGE);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case CLICK_MESSAGE:
if (null != mCallback) {
mCallback.onRealClick();
}
break;
default:
break;
}
}
public interface Callback {
public void onRealClick();
}
}

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