Java版本待完善

This commit is contained in:
huanghaijie
2018-10-12 20:00:32 +08:00
parent 413d9b78a0
commit f578c9be87
21 changed files with 623 additions and 3 deletions

1
pagelayout-java/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,34 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
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'
}

21
pagelayout-java/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.hankkin.pagelayout_java;
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.hankkin.pagelayout_java.test", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hankkin.pagelayout_java" />

View File

@@ -0,0 +1,269 @@
package com.hankkin.pagelayout_java;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.RequiresApi;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
public class BlinkLayout extends LinearLayout {
private Paint maskPaint;
private ValueAnimator maskAnimator;
private Bitmap localAvailableBitmap;
private Bitmap localMaskBitmap;
private Bitmap destinationBitmap;
private Bitmap sourceMaskBitmap;
private Canvas canvasForRendering;
private int maskOffsetX;
private boolean isAnimationStarted;
private int shimmerAnimationDuration;
@ColorInt
private int shimmerColor;
public BlinkLayout(Context context) {
this(context, null);
}
public BlinkLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BlinkLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setWillNotDraw(false);
maskPaint = new Paint();
maskPaint.setAntiAlias(true);
maskPaint.setDither(true);
maskPaint.setFilterBitmap(true);
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.BlinkLayout,
0, 0);
try {
shimmerAnimationDuration = a.getInteger(R.styleable.BlinkLayout_blink_animation_duration, 1500);
shimmerColor = a.getColor(R.styleable.BlinkLayout_blink_color, ContextCompat.getColor(context, R.color.shimmer_color));
} finally {
a.recycle();
}
}
@Override
protected void onDetachedFromWindow() {
resetShimmering();
super.onDetachedFromWindow();
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (!isAnimationStarted || getWidth() <= 0 || getHeight() <= 0) {
super.dispatchDraw(canvas);
} else {
dispatchDrawUsingBitmap(canvas);
}
}
public void startShimmerAnimation() {
if (isAnimationStarted) {
return;
}
if (getWidth() == 0) {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
BlinkLayout.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
startShimmerAnimation();
}
});
return;
}
Animator animator = getShimmerAnimation();
animator.start();
isAnimationStarted = true;
}
public void stopShimmerAnimation() {
resetShimmering();
}
public void setShimmerColor(int shimmerColor) {
this.shimmerColor = shimmerColor;
if (isAnimationStarted) {
resetShimmering();
startShimmerAnimation();
}
}
private void dispatchDrawUsingBitmap(Canvas canvas) {
super.dispatchDraw(canvas);
localAvailableBitmap = getDestinationBitmap();
if (localAvailableBitmap == null) {
return;
}
if (canvasForRendering == null) {
canvasForRendering = new Canvas(localAvailableBitmap);
}
drawMask(canvasForRendering);
canvas.save();
canvas.clipRect(maskOffsetX, 0, maskOffsetX + getWidth() / 2, getHeight());
canvas.drawBitmap(localAvailableBitmap, 0, 0, null);
canvas.restore();
localAvailableBitmap = null;
}
private void drawMask(Canvas renderCanvas) {
localMaskBitmap = getSourceMaskBitmap();
if (localMaskBitmap == null) {
return;
}
renderCanvas.save();
renderCanvas.clipRect(maskOffsetX, 0,
maskOffsetX + localMaskBitmap.getWidth(),
getHeight());
super.dispatchDraw(renderCanvas);
renderCanvas.drawBitmap(localMaskBitmap, maskOffsetX, 0, maskPaint);
renderCanvas.restore();
localMaskBitmap = null;
}
private void resetShimmering() {
if (maskAnimator != null) {
maskAnimator.end();
maskAnimator.removeAllUpdateListeners();
}
maskAnimator = null;
isAnimationStarted = false;
releaseBitMaps();
}
private void releaseBitMaps() {
if (sourceMaskBitmap != null) {
sourceMaskBitmap.recycle();
sourceMaskBitmap = null;
}
if (destinationBitmap != null) {
destinationBitmap.recycle();
destinationBitmap = null;
}
canvasForRendering = null;
}
private Bitmap getDestinationBitmap() {
if (destinationBitmap == null) {
destinationBitmap = createBitmap(getWidth(), getHeight());
}
return destinationBitmap;
}
private Bitmap getSourceMaskBitmap() {
if (sourceMaskBitmap != null) {
return sourceMaskBitmap;
}
int width = getWidth() / 2;
int height = getHeight();
sourceMaskBitmap = createBitmap(width, height);
Canvas canvas = new Canvas(sourceMaskBitmap);
LinearGradient gradient = new LinearGradient(
0, 0,
width, 0,
new int[]{Color.TRANSPARENT, shimmerColor, shimmerColor, Color.TRANSPARENT},
new float[]{0.25F, 0.5F, 0.5F, 0.75F},
Shader.TileMode.CLAMP);
canvas.rotate(20, width / 2, height / 2);
Paint paint = new Paint();
paint.setShader(gradient);
int padding = (int) (Math.sqrt(2) * Math.max(width, height)) / 2;
canvas.drawRect(0, -padding, width, height + padding, paint);
return sourceMaskBitmap;
}
private Animator getShimmerAnimation() {
if (maskAnimator != null) {
return maskAnimator;
}
final int animationToX = getWidth();
final int animationFromX = -animationToX;
final int shimmerBitmapWidth = getWidth() / 2;
final int shimmerAnimationFullLength = animationToX - animationFromX;
maskAnimator = ValueAnimator.ofFloat(0.0F, 1.0F);
maskAnimator.setDuration(shimmerAnimationDuration);
maskAnimator.setRepeatCount(ObjectAnimator.INFINITE);
final float[] value = new float[1];
maskAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
value[0] = (Float) animation.getAnimatedValue();
maskOffsetX = ((int) (animationFromX + shimmerAnimationFullLength * value[0]));
if (maskOffsetX + shimmerBitmapWidth >= 0) {
invalidate();
}
}
});
return maskAnimator;
}
private Bitmap createBitmap(int width, int height) {
try {
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
} catch (OutOfMemoryError e) {
System.gc();
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
}
}

View File

@@ -0,0 +1,148 @@
package com.hankkin.pagelayout_java;
import android.content.Context;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
/**
* Created by ${Hankkin} on 2018/10/12.
*/
public class PageLayout extends FrameLayout {
enum State{
EMPTY_TYPE,
LOADING_TYPE,
ERROR_TYPE,
CONTENT_TYPE,
CUSTOM_TYPE
}
private View mLoading;
private View mEmpty;
private View mError;
private View mContent;
private View mCustom;
private Context mContext;
private BlinkLayout mBlinkLayout;
private State mCurrentState;
public PageLayout(@NonNull Context context) {
super(context);
}
public PageLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PageLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void showView(final State state){
if (Looper.myLooper() == Looper.getMainLooper()){
changeView(state);
}
else {
post(new Runnable() {
@Override
public void run() {
changeView(state);
}
});
}
}
private void changeView(State state){
mCurrentState = state;
switch (state){
case LOADING_TYPE:
mLoading.setVisibility(VISIBLE);
break;
case EMPTY_TYPE:
mEmpty.setVisibility(VISIBLE);
break;
case ERROR_TYPE:
mError.setVisibility(VISIBLE);
break;
case CUSTOM_TYPE:
mCustom.setVisibility(VISIBLE);
break;
case CONTENT_TYPE:
mContent.setVisibility(VISIBLE);
break;
}
}
public void showLoading(){
showView(State.LOADING_TYPE);
if (mBlinkLayout != null){
mBlinkLayout.startShimmerAnimation();
}
}
public void showError(){
showView(State.ERROR_TYPE);
}
public void showEmpty(){
showView(State.EMPTY_TYPE);
}
public void hide(){
showView(State.CONTENT_TYPE);
if (mBlinkLayout!= null){
mBlinkLayout.stopShimmerAnimation();
}
}
public void showCustom(){
showView(State.CUSTOM_TYPE);
}
class Builder{
private PageLayout mPageLayout;
private LayoutInflater mInflater;
private Context mContext;
private TextView mTvEmpty;
private TextView mTvError;
private TextView mTvErrorRetry;
private TextView mTvLoading;
private TextView mTvLoadingBlink;
private BlinkLayout mBlinkLayout;
private OnRetryClickListener mOnRetryClickListener;
public Builder(Context context) {
mContext = context;
this.mPageLayout = new PageLayout(mContext);
this.mInflater = LayoutInflater.from(mContext);
}
private void initDefault(){
if (mPageLayout.mEmpty == null){
setDefaultEmpty();
}
}
private void setDefaultEmpty(){
mPageLayout.mEmpty = mInflater.inflate(R.layout.layout_empty, mPageLayout, false);
mTvEmpty = mPageLayout.mEmpty.findViewById(R.id.tv_page_empty);
mPageLayout.mEmpty.setVisibility(GONE);
mPageLayout.addView(mPageLayout.mEmpty);
}
private void setDefaultError()
}
interface OnRetryClickListener{
void onRetry();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:gravity="center"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:drawableTop="@drawable/pic_empty"
android:text="@string/page_empty"
android:id="@+id/tv_page_empty"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<com.hankkin.pagelayout.BlinkLayout
android:gravity="center"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:drawableTop="@drawable/pic_error"
android:gravity="center"
android:text="@string/page_error"
android:id="@+id/tv_page_error"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:textColor="@color/colorPrimary"
android:layout_marginTop="10dp"
android:textSize="13sp"
android:gravity="center"
android:text="@string/page_error_retry"
android:id="@+id/tv_page_error_retry"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.hankkin.pagelayout.BlinkLayout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<com.hankkin.pagelayout.BlinkLayout
android:id="@+id/blinklayout"
android:gravity="center"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="PageLaout"
android:textStyle="bold"
android:textSize="25sp"
android:textColor="@color/blink_color"
android:id="@+id/tv_page_loading_blink"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.hankkin.pagelayout.BlinkLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#ff662c</color>
<color name="colorPrimaryDark">#ff662c</color>
<color name="colorAccent">#ff662c</color>
<color name="blink_color">#b1b1b1</color>
<color name="shimmer_color">#a2878787</color>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<string name="app_name">EmptyPage</string>
<string name="page_error">网络开小差了</string>
<string name="page_error_retry">点击重试</string>
<string name="page_empty">暂无新数据</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<declare-styleable name="BlinkLayout">
<attr name="blink_color" format="color|reference"/>
<attr name="blink_animation_duration" format="integer|reference"/>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,17 @@
package com.hankkin.pagelayout_java;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}