Bläddra i källkod

初始化项目

詹子聪 5 år sedan
incheckning
1ab7984dd4
100 ändrade filer med 4880 tillägg och 0 borttagningar
  1. 14 0
      .gitignore
  2. 1 0
      adapter/.gitignore
  3. 33 0
      adapter/build.gradle
  4. 17 0
      adapter/proguard-rules.pro
  5. 9 0
      adapter/src/main/AndroidManifest.xml
  6. 8 0
      adapter/src/main/java/com/itant/library/Placeholder.java
  7. 41 0
      adapter/src/main/java/com/itant/library/abslistview/CommonAdapter.java
  8. 98 0
      adapter/src/main/java/com/itant/library/abslistview/MultiItemTypeAdapter.java
  9. 290 0
      adapter/src/main/java/com/itant/library/abslistview/ViewHolder.java
  10. 20 0
      adapter/src/main/java/com/itant/library/abslistview/base/ItemViewDelegate.java
  11. 134 0
      adapter/src/main/java/com/itant/library/abslistview/base/ItemViewDelegateManager.java
  12. 54 0
      adapter/src/main/java/com/itant/library/recyclerview/CommonAdapter.java
  13. 132 0
      adapter/src/main/java/com/itant/library/recyclerview/MultiItemTypeAdapter.java
  14. 16 0
      adapter/src/main/java/com/itant/library/recyclerview/base/ItemViewDelegate.java
  15. 116 0
      adapter/src/main/java/com/itant/library/recyclerview/base/ItemViewDelegateManager.java
  16. 311 0
      adapter/src/main/java/com/itant/library/recyclerview/base/ViewHolder.java
  17. 55 0
      adapter/src/main/java/com/itant/library/recyclerview/utils/WrapperUtils.java
  18. 128 0
      adapter/src/main/java/com/itant/library/recyclerview/wrapper/EmptyWrapper.java
  19. 152 0
      adapter/src/main/java/com/itant/library/recyclerview/wrapper/HeaderAndFooterWrapper.java
  20. 161 0
      adapter/src/main/java/com/itant/library/recyclerview/wrapper/LoadMoreWrapper.java
  21. 13 0
      adapter/src/main/res/anim/anim_bottom_in.xml
  22. 4 0
      adapter/src/main/res/values-v21/dimen.xml
  23. 11 0
      adapter/src/main/res/values/color.xml
  24. 5 0
      adapter/src/main/res/values/dimen.xml
  25. 3 0
      adapter/src/main/res/values/strings.xml
  26. 22 0
      adapter/src/main/res/values/styles.xml
  27. 1 0
      app/.gitignore
  28. 42 0
      app/build.gradle
  29. 21 0
      app/proguard-rules.pro
  30. 27 0
      app/src/androidTest/java/com/miekir/ocr/ExampleInstrumentedTest.java
  31. 33 0
      app/src/main/AndroidManifest.xml
  32. 445 0
      app/src/main/java/com/miekir/ocr/CameraActivity.java
  33. 52 0
      app/src/main/java/com/miekir/ocr/MainActivity.java
  34. 17 0
      app/src/main/java/com/miekir/ocr/base/BaseCameraActivity.java
  35. 127 0
      app/src/main/java/com/miekir/ocr/tool/Utils.java
  36. 65 0
      app/src/main/java/com/miekir/ocr/view/AutoFitTextureView.java
  37. 34 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  38. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  39. 29 0
      app/src/main/res/layout/activity_camera.xml
  40. 9 0
      app/src/main/res/layout/activity_main.xml
  41. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  42. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  43. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  44. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  45. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  46. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  47. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  48. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  49. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  50. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  51. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  52. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  53. 7 0
      app/src/main/res/values/colors.xml
  54. 3 0
      app/src/main/res/values/strings.xml
  55. 15 0
      app/src/main/res/values/styles.xml
  56. 17 0
      app/src/test/java/com/miekir/ocr/ExampleUnitTest.java
  57. 37 0
      build.gradle
  58. 1 0
      common/.gitignore
  59. 65 0
      common/build.gradle
  60. 0 0
      common/consumer-rules.pro
  61. 21 0
      common/proguard-rules.pro
  62. 27 0
      common/src/androidTest/java/com/genlot/common/ExampleInstrumentedTest.java
  63. 27 0
      common/src/androidTest/java/com/miekir/common/ExampleInstrumentedTest.java
  64. 10 0
      common/src/main/AndroidManifest.xml
  65. 31 0
      common/src/main/java/com/miekir/common/adapter/TabFragmentAdapter.java
  66. 59 0
      common/src/main/java/com/miekir/common/provider/CommonInstaller.java
  67. 30 0
      common/src/main/java/com/miekir/common/utils/ActivityTool.java
  68. 16 0
      common/src/main/java/com/miekir/common/utils/SizeTool.java
  69. 68 0
      common/src/main/java/com/miekir/common/utils/ToastTool.java
  70. 32 0
      common/src/main/java/com/miekir/common/utils/ViewTool.java
  71. 141 0
      common/src/main/java/com/miekir/common/view/AlignTextView.java
  72. 237 0
      common/src/main/java/com/miekir/common/view/RoundedCornersTransformation.java
  73. 45 0
      common/src/main/java/com/miekir/common/widget/ScrollableViewPager.java
  74. 3 0
      common/src/main/res/values/strings.xml
  75. 17 0
      common/src/test/java/com/genlot/common/ExampleUnitTest.java
  76. 17 0
      common/src/test/java/com/miekir/common/ExampleUnitTest.java
  77. 20 0
      gradle.properties
  78. BIN
      gradle/wrapper/gradle-wrapper.jar
  79. 6 0
      gradle/wrapper/gradle-wrapper.properties
  80. 172 0
      gradlew
  81. 84 0
      gradlew.bat
  82. 10 0
      local.properties
  83. 1 0
      mvp/.gitignore
  84. 34 0
      mvp/build.gradle
  85. 0 0
      mvp/consumer-rules.pro
  86. 21 0
      mvp/proguard-rules.pro
  87. 27 0
      mvp/src/androidTest/java/com/miekir/mvp/ExampleInstrumentedTest.java
  88. 2 0
      mvp/src/main/AndroidManifest.xml
  89. 139 0
      mvp/src/main/java/com/miekir/mvp/base/BaseActivity.java
  90. 106 0
      mvp/src/main/java/com/miekir/mvp/base/BaseFragment.java
  91. 24 0
      mvp/src/main/java/com/miekir/mvp/presenter/BasePresenter.java
  92. 15 0
      mvp/src/main/java/com/miekir/mvp/presenter/InjectPresenter.java
  93. 72 0
      mvp/src/main/java/com/miekir/mvp/view/BaseMVPActivity.java
  94. 65 0
      mvp/src/main/java/com/miekir/mvp/view/BaseMVPFragment.java
  95. 12 0
      mvp/src/main/java/com/miekir/mvp/view/IView.java
  96. 112 0
      mvp/src/main/java/com/miekir/mvp/widget/LVCircularRing.java
  97. 64 0
      mvp/src/main/java/com/miekir/mvp/widget/LoadingDialog.java
  98. 12 0
      mvp/src/main/res/drawable/shape_dialog_bg.xml
  99. 26 0
      mvp/src/main/res/layout/dialog_loading_view.xml
  100. 0 0
      mvp/src/main/res/values/strings.xml

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx

+ 1 - 0
adapter/.gitignore

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

+ 33 - 0
adapter/build.gradle

@@ -0,0 +1,33 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion versions.compileSdk
+    buildToolsVersion versions.buildTools
+
+    defaultConfig {
+        minSdkVersion versions.minSdk
+        targetSdkVersion versions.targetSdk
+        versionCode 1
+        versionName "1.0"
+
+        //testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    /*androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    testCompile 'junit:junit:4.12'*/
+
+    api 'androidx.appcompat:appcompat:1.3.0-alpha01'
+    api 'com.google.android.material:material:1.1.0'
+}

+ 17 - 0
adapter/proguard-rules.pro

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

+ 9 - 0
adapter/src/main/AndroidManifest.xml

@@ -0,0 +1,9 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.itant.library">
+
+    <application  android:label="@string/app_name">
+        <!--android:allowBackup="true"
+        android:supportsRtl="true"-->
+
+    </application>
+
+</manifest>

+ 8 - 0
adapter/src/main/java/com/itant/library/Placeholder.java

@@ -0,0 +1,8 @@
+package com.itant.library;
+
+/**
+ * Created by iTant on 2017/3/27.
+ */
+
+public class Placeholder {
+}

+ 41 - 0
adapter/src/main/java/com/itant/library/abslistview/CommonAdapter.java

@@ -0,0 +1,41 @@
+package com.itant.library.abslistview;
+
+import android.content.Context;
+
+
+import com.itant.library.abslistview.base.ItemViewDelegate;
+
+import java.util.List;
+
+public abstract class CommonAdapter<T> extends MultiItemTypeAdapter<T>
+{
+
+    public CommonAdapter(Context context, final int layoutId, List<T> datas)
+    {
+        super(context, datas);
+
+        addItemViewDelegate(new ItemViewDelegate<T>()
+        {
+            @Override
+            public int getItemViewLayoutId()
+            {
+                return layoutId;
+            }
+
+            @Override
+            public boolean isForViewType(T item, int position)
+            {
+                return true;
+            }
+
+            @Override
+            public void convert(ViewHolder holder, T t, int position)
+            {
+                CommonAdapter.this.convert(holder, t, position);
+            }
+        });
+    }
+
+    protected abstract void convert(ViewHolder viewHolder, T item, int position);
+
+}

+ 98 - 0
adapter/src/main/java/com/itant/library/abslistview/MultiItemTypeAdapter.java

@@ -0,0 +1,98 @@
+package com.itant.library.abslistview;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.itant.library.abslistview.base.ItemViewDelegate;
+import com.itant.library.abslistview.base.ItemViewDelegateManager;
+
+import java.util.List;
+
+public class MultiItemTypeAdapter<T> extends BaseAdapter {
+    protected Context mContext;
+    protected List<T> mDatas;
+
+    private ItemViewDelegateManager mItemViewDelegateManager;
+
+
+    public MultiItemTypeAdapter(Context context, List<T> datas) {
+        this.mContext = context;
+        this.mDatas = datas;
+        mItemViewDelegateManager = new ItemViewDelegateManager();
+    }
+
+    public MultiItemTypeAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate) {
+        mItemViewDelegateManager.addDelegate(itemViewDelegate);
+        return this;
+    }
+
+    private boolean useItemViewDelegateManager() {
+        return mItemViewDelegateManager.getItemViewDelegateCount() > 0;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        if (useItemViewDelegateManager())
+            return mItemViewDelegateManager.getItemViewDelegateCount();
+        return super.getViewTypeCount();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (useItemViewDelegateManager()) {
+            int viewType = mItemViewDelegateManager.getItemViewType(mDatas.get(position), position);
+            return viewType;
+        }
+        return super.getItemViewType(position);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(mDatas.get(position), position);
+        int layoutId = itemViewDelegate.getItemViewLayoutId();
+        ViewHolder viewHolder = null ;
+        if (convertView == null)
+        {
+            View itemView = LayoutInflater.from(mContext).inflate(layoutId, parent,
+                    false);
+            viewHolder = new ViewHolder(mContext, itemView, parent, position);
+            viewHolder.mLayoutId = layoutId;
+            onViewHolderCreated(viewHolder,viewHolder.getConvertView());
+        } else
+        {
+            viewHolder = (ViewHolder) convertView.getTag();
+            viewHolder.mPosition = position;
+        }
+
+
+        convert(viewHolder, getItem(position), position);
+        return viewHolder.getConvertView();
+    }
+
+    protected void convert(ViewHolder viewHolder, T item, int position) {
+        mItemViewDelegateManager.convert(viewHolder, item, position);
+    }
+
+    public void onViewHolderCreated(ViewHolder holder , View itemView )
+    {}
+
+    @Override
+    public int getCount() {
+        return mDatas.size();
+    }
+
+    @Override
+    public T getItem(int position) {
+        return mDatas.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+
+}

+ 290 - 0
adapter/src/main/java/com/itant/library/abslistview/ViewHolder.java

@@ -0,0 +1,290 @@
+package com.itant.library.abslistview;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.util.Linkify;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RatingBar;
+import android.widget.TextView;
+
+public class ViewHolder
+{
+    private SparseArray<View> mViews;
+    protected int mPosition;
+    private View mConvertView;
+    private Context mContext;
+    protected int mLayoutId;
+
+    public ViewHolder(Context context, View itemView, ViewGroup parent, int position)
+    {
+        mContext = context;
+        mConvertView = itemView;
+        mPosition = position;
+        mViews = new SparseArray<View>();
+        mConvertView.setTag(this);
+    }
+
+
+    public static ViewHolder get(Context context, View convertView,
+                                 ViewGroup parent, int layoutId, int position)
+    {
+        if (convertView == null)
+        {
+            View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
+                    false);
+            ViewHolder holder = new ViewHolder(context, itemView, parent, position);
+            holder.mLayoutId = layoutId;
+            return holder;
+        } else
+        {
+            ViewHolder holder = (ViewHolder) convertView.getTag();
+            holder.mPosition = position;
+            return holder;
+        }
+    }
+
+
+    /**
+     * 通过viewId获取控件
+     *
+     * @param viewId
+     * @return
+     */
+    public <T extends View> T getView(int viewId)
+    {
+        View view = mViews.get(viewId);
+        if (view == null)
+        {
+            view = mConvertView.findViewById(viewId);
+            mViews.put(viewId, view);
+        }
+        return (T) view;
+    }
+
+    public View getConvertView()
+    {
+        return mConvertView;
+    }
+
+    public int getLayoutId()
+    {
+        return mLayoutId;
+    }
+
+    public void updatePosition(int position)
+    {
+        mPosition = position;
+    }
+
+    public int getItemPosition()
+    {
+        return mPosition;
+    }
+
+
+    /****以下为辅助方法*****/
+
+    /**
+     * 设置TextView的值
+     *
+     * @param viewId
+     * @param text
+     * @return
+     */
+    public ViewHolder setText(int viewId, String text)
+    {
+        TextView tv = getView(viewId);
+        tv.setText(text);
+        return this;
+    }
+
+    public ViewHolder setImageResource(int viewId, int resId)
+    {
+        ImageView view = getView(viewId);
+        view.setImageResource(resId);
+        return this;
+    }
+
+    public ViewHolder setImageBitmap(int viewId, Bitmap bitmap)
+    {
+        ImageView view = getView(viewId);
+        view.setImageBitmap(bitmap);
+        return this;
+    }
+
+    public ViewHolder setImageDrawable(int viewId, Drawable drawable)
+    {
+        ImageView view = getView(viewId);
+        view.setImageDrawable(drawable);
+        return this;
+    }
+
+    public ViewHolder setBackgroundColor(int viewId, int color)
+    {
+        View view = getView(viewId);
+        view.setBackgroundColor(color);
+        return this;
+    }
+
+    public ViewHolder setBackgroundRes(int viewId, int backgroundRes)
+    {
+        View view = getView(viewId);
+        view.setBackgroundResource(backgroundRes);
+        return this;
+    }
+
+    public ViewHolder setTextColor(int viewId, int textColor)
+    {
+        TextView view = getView(viewId);
+        view.setTextColor(textColor);
+        return this;
+    }
+
+    public ViewHolder setTextColorRes(int viewId, int textColorRes)
+    {
+        TextView view = getView(viewId);
+        view.setTextColor(mContext.getResources().getColor(textColorRes));
+        return this;
+    }
+
+    @SuppressLint("NewApi")
+    public ViewHolder setAlpha(int viewId, float value)
+    {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
+        {
+            getView(viewId).setAlpha(value);
+        } else
+        {
+            // Pre-honeycomb hack to set Alpha value
+            AlphaAnimation alpha = new AlphaAnimation(value, value);
+            alpha.setDuration(0);
+            alpha.setFillAfter(true);
+            getView(viewId).startAnimation(alpha);
+        }
+        return this;
+    }
+
+    public ViewHolder setVisible(int viewId, boolean visible)
+    {
+        View view = getView(viewId);
+        view.setVisibility(visible ? View.VISIBLE : View.GONE);
+        return this;
+    }
+
+    public ViewHolder linkify(int viewId)
+    {
+        TextView view = getView(viewId);
+        Linkify.addLinks(view, Linkify.ALL);
+        return this;
+    }
+
+    public ViewHolder setTypeface(Typeface typeface, int... viewIds)
+    {
+        for (int viewId : viewIds)
+        {
+            TextView view = getView(viewId);
+            view.setTypeface(typeface);
+            view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
+        }
+        return this;
+    }
+
+    public ViewHolder setProgress(int viewId, int progress)
+    {
+        ProgressBar view = getView(viewId);
+        view.setProgress(progress);
+        return this;
+    }
+
+    public ViewHolder setProgress(int viewId, int progress, int max)
+    {
+        ProgressBar view = getView(viewId);
+        view.setMax(max);
+        view.setProgress(progress);
+        return this;
+    }
+
+    public ViewHolder setMax(int viewId, int max)
+    {
+        ProgressBar view = getView(viewId);
+        view.setMax(max);
+        return this;
+    }
+
+    public ViewHolder setRating(int viewId, float rating)
+    {
+        RatingBar view = getView(viewId);
+        view.setRating(rating);
+        return this;
+    }
+
+    public ViewHolder setRating(int viewId, float rating, int max)
+    {
+        RatingBar view = getView(viewId);
+        view.setMax(max);
+        view.setRating(rating);
+        return this;
+    }
+
+    public ViewHolder setTag(int viewId, Object tag)
+    {
+        View view = getView(viewId);
+        view.setTag(tag);
+        return this;
+    }
+
+    public ViewHolder setTag(int viewId, int key, Object tag)
+    {
+        View view = getView(viewId);
+        view.setTag(key, tag);
+        return this;
+    }
+
+    public ViewHolder setChecked(int viewId, boolean checked)
+    {
+        Checkable view = (Checkable) getView(viewId);
+        view.setChecked(checked);
+        return this;
+    }
+
+    /**
+     * 关于事件的
+     */
+    public ViewHolder setOnClickListener(int viewId,
+                                         View.OnClickListener listener)
+    {
+        View view = getView(viewId);
+        view.setOnClickListener(listener);
+        return this;
+    }
+
+    public ViewHolder setOnTouchListener(int viewId,
+                                         View.OnTouchListener listener)
+    {
+        View view = getView(viewId);
+        view.setOnTouchListener(listener);
+        return this;
+    }
+
+    public ViewHolder setOnLongClickListener(int viewId,
+                                             View.OnLongClickListener listener)
+    {
+        View view = getView(viewId);
+        view.setOnLongClickListener(listener);
+        return this;
+    }
+
+
+}

+ 20 - 0
adapter/src/main/java/com/itant/library/abslistview/base/ItemViewDelegate.java

@@ -0,0 +1,20 @@
+package com.itant.library.abslistview.base;
+
+
+import com.itant.library.abslistview.ViewHolder;
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public interface ItemViewDelegate<T>
+{
+
+    public abstract int getItemViewLayoutId();
+
+    public abstract boolean isForViewType(T item, int position);
+
+    public abstract void convert(ViewHolder holder, T t, int position);
+
+
+
+}

+ 134 - 0
adapter/src/main/java/com/itant/library/abslistview/base/ItemViewDelegateManager.java

@@ -0,0 +1,134 @@
+package com.itant.library.abslistview.base;
+
+
+import androidx.collection.SparseArrayCompat;
+
+import com.itant.library.abslistview.ViewHolder;
+
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public class ItemViewDelegateManager<T>
+{
+    SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat();
+
+    public int getItemViewDelegateCount()
+    {
+        return delegates.size();
+    }
+
+    public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate)
+    {
+        int viewType = delegates.size();
+        if (delegate != null)
+        {
+            delegates.put(viewType, delegate);
+            viewType++;
+        }
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate)
+    {
+        if (delegates.get(viewType) != null)
+        {
+            throw new IllegalArgumentException(
+                    "An ItemViewDelegate is already registered for the viewType = "
+                            + viewType
+                            + ". Already registered ItemViewDelegate is "
+                            + delegates.get(viewType));
+        }
+        delegates.put(viewType, delegate);
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> removeDelegate(ItemViewDelegate<T> delegate)
+    {
+        if (delegate == null)
+        {
+            throw new NullPointerException("ItemViewDelegate is null");
+        }
+        int indexToRemove = delegates.indexOfValue(delegate);
+
+        if (indexToRemove >= 0)
+        {
+            delegates.removeAt(indexToRemove);
+        }
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> removeDelegate(int itemType)
+    {
+        int indexToRemove = delegates.indexOfKey(itemType);
+
+        if (indexToRemove >= 0)
+        {
+            delegates.removeAt(indexToRemove);
+        }
+        return this;
+    }
+
+    public int getItemViewType(T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = delegatesCount - 1; i >= 0; i--)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+            if (delegate.isForViewType(item, position))
+            {
+                return delegates.keyAt(i);
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegate added that matches position=" + position + " in data source");
+    }
+
+    public void convert(ViewHolder holder, T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = 0; i < delegatesCount; i++)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+
+            if (delegate.isForViewType(item, position))
+            {
+                delegate.convert(holder, item, position);
+                return;
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegateManager added that matches position=" + position + " in data source");
+    }
+
+
+    public int getItemViewLayoutId(int viewType)
+    {
+        return delegates.get(viewType).getItemViewLayoutId();
+    }
+
+    public int getItemViewType(ItemViewDelegate itemViewDelegate)
+    {
+        return delegates.indexOfValue(itemViewDelegate);
+    }
+
+    public ItemViewDelegate getItemViewDelegate(T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = delegatesCount - 1; i >= 0; i--)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+            if (delegate.isForViewType(item, position))
+            {
+                return delegate;
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegate added that matches position=" + position + " in data source");
+    }
+
+    public int getItemViewLayoutId(T item, int position)
+    {
+        return getItemViewDelegate(item,position).getItemViewLayoutId();
+    }
+}

+ 54 - 0
adapter/src/main/java/com/itant/library/recyclerview/CommonAdapter.java

@@ -0,0 +1,54 @@
+package com.itant.library.recyclerview;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+
+import com.itant.library.recyclerview.base.ItemViewDelegate;
+import com.itant.library.recyclerview.base.ViewHolder;
+
+import java.util.List;
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public abstract class CommonAdapter<T> extends MultiItemTypeAdapter<T>
+{
+    protected Context mContext;
+    protected int mLayoutId;
+    protected List<T> mDatas;
+    protected LayoutInflater mInflater;
+
+    public CommonAdapter(final Context context, final int layoutId, List<T> datas)
+    {
+        super(context, datas);
+        mContext = context;
+        mInflater = LayoutInflater.from(context);
+        mLayoutId = layoutId;
+        mDatas = datas;
+
+        addItemViewDelegate(new ItemViewDelegate<T>()
+        {
+            @Override
+            public int getItemViewLayoutId()
+            {
+                return layoutId;
+            }
+
+            @Override
+            public boolean isForViewType( T item, int position)
+            {
+                return true;
+            }
+
+            @Override
+            public void convert(ViewHolder holder, T t, int position)
+            {
+                CommonAdapter.this.convert(holder, t, position);
+            }
+        });
+    }
+
+    protected abstract void convert(ViewHolder holder, T t, int position);
+
+
+}

+ 132 - 0
adapter/src/main/java/com/itant/library/recyclerview/MultiItemTypeAdapter.java

@@ -0,0 +1,132 @@
+package com.itant.library.recyclerview;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.itant.library.recyclerview.base.ItemViewDelegate;
+import com.itant.library.recyclerview.base.ItemViewDelegateManager;
+import com.itant.library.recyclerview.base.ViewHolder;
+
+import java.util.List;
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public class MultiItemTypeAdapter<T> extends RecyclerView.Adapter<ViewHolder> {
+    protected Context mContext;
+    protected List<T> mDatas;
+
+    protected ItemViewDelegateManager mItemViewDelegateManager;
+    protected OnItemClickListener mOnItemClickListener;
+
+
+    public MultiItemTypeAdapter(Context context, List<T> datas) {
+        mContext = context;
+        mDatas = datas;
+        mItemViewDelegateManager = new ItemViewDelegateManager();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (!useItemViewDelegateManager()) return super.getItemViewType(position);
+        return mItemViewDelegateManager.getItemViewType(mDatas.get(position), position);
+    }
+
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType);
+        int layoutId = itemViewDelegate.getItemViewLayoutId();
+        ViewHolder holder = ViewHolder.createViewHolder(mContext, parent, layoutId);
+        onViewHolderCreated(holder,holder.getConvertView());
+        setListener(parent, holder, viewType);
+        return holder;
+    }
+
+    public void onViewHolderCreated(ViewHolder holder,View itemView){
+
+    }
+
+    public void convert(ViewHolder holder, T t) {
+        mItemViewDelegateManager.convert(holder, t, holder.getAdapterPosition());
+    }
+
+    protected boolean isEnabled(int viewType) {
+        return true;
+    }
+
+
+    protected void setListener(final ViewGroup parent, final ViewHolder viewHolder, int viewType) {
+        if (!isEnabled(viewType)) return;
+        viewHolder.getConvertView().setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mOnItemClickListener != null) {
+                    int position = viewHolder.getAdapterPosition();
+                    mOnItemClickListener.onItemClick(v, viewHolder , position);
+                }
+            }
+        });
+
+        viewHolder.getConvertView().setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (mOnItemClickListener != null) {
+                    int position = viewHolder.getAdapterPosition();
+                    return mOnItemClickListener.onItemLongClick(v, viewHolder, position);
+                }
+                return false;
+            }
+        });
+    }
+
+    private int mLastPosition=-1;// 设为-1是为了让动画从第0个item出现
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        convert(holder, mDatas.get(position));
+        /*if (position > mLastPosition && !isFooterPosition(position)) {
+            // 一个一个出现的动画
+            Animation animation = AnimationUtils.loadAnimation(holder.itemView.getContext(), R.anim.anim_bottom_in);
+            holder.itemView.startAnimation(animation);
+            mLastPosition = position;
+        }*/
+    }
+
+    @Override
+    public int getItemCount() {
+        int itemCount = mDatas.size();
+        return itemCount;
+    }
+
+
+    public List<T> getDatas() {
+        return mDatas;
+    }
+
+    public MultiItemTypeAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate) {
+        mItemViewDelegateManager.addDelegate(itemViewDelegate);
+        return this;
+    }
+
+    public MultiItemTypeAdapter addItemViewDelegate(int viewType, ItemViewDelegate<T> itemViewDelegate) {
+        mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate);
+        return this;
+    }
+
+    protected boolean useItemViewDelegateManager() {
+        return mItemViewDelegateManager.getItemViewDelegateCount() > 0;
+    }
+
+    public interface OnItemClickListener {
+        void onItemClick(View view, RecyclerView.ViewHolder holder, int position);
+
+        boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position);
+    }
+
+    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
+        this.mOnItemClickListener = onItemClickListener;
+    }
+}

+ 16 - 0
adapter/src/main/java/com/itant/library/recyclerview/base/ItemViewDelegate.java

@@ -0,0 +1,16 @@
+package com.itant.library.recyclerview.base;
+
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public interface ItemViewDelegate<T>
+{
+
+    int getItemViewLayoutId();
+
+    boolean isForViewType(T item, int position);
+
+    void convert(ViewHolder holder, T t, int position);
+
+}

+ 116 - 0
adapter/src/main/java/com/itant/library/recyclerview/base/ItemViewDelegateManager.java

@@ -0,0 +1,116 @@
+package com.itant.library.recyclerview.base;
+
+
+import androidx.collection.SparseArrayCompat;
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public class ItemViewDelegateManager<T>
+{
+    SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat();
+
+    public int getItemViewDelegateCount()
+    {
+        return delegates.size();
+    }
+
+    public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate)
+    {
+        int viewType = delegates.size();
+        if (delegate != null)
+        {
+            delegates.put(viewType, delegate);
+            viewType++;
+        }
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate)
+    {
+        if (delegates.get(viewType) != null)
+        {
+            throw new IllegalArgumentException(
+                    "An ItemViewDelegate is already registered for the viewType = "
+                            + viewType
+                            + ". Already registered ItemViewDelegate is "
+                            + delegates.get(viewType));
+        }
+        delegates.put(viewType, delegate);
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> removeDelegate(ItemViewDelegate<T> delegate)
+    {
+        if (delegate == null)
+        {
+            throw new NullPointerException("ItemViewDelegate is null");
+        }
+        int indexToRemove = delegates.indexOfValue(delegate);
+
+        if (indexToRemove >= 0)
+        {
+            delegates.removeAt(indexToRemove);
+        }
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> removeDelegate(int itemType)
+    {
+        int indexToRemove = delegates.indexOfKey(itemType);
+
+        if (indexToRemove >= 0)
+        {
+            delegates.removeAt(indexToRemove);
+        }
+        return this;
+    }
+
+    public int getItemViewType(T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = delegatesCount - 1; i >= 0; i--)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+            if (delegate.isForViewType( item, position))
+            {
+                return delegates.keyAt(i);
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegate added that matches position=" + position + " in data source");
+    }
+
+    public void convert(ViewHolder holder, T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = 0; i < delegatesCount; i++)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+
+            if (delegate.isForViewType( item, position))
+            {
+                delegate.convert(holder, item, position);
+                return;
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegateManager added that matches position=" + position + " in data source");
+    }
+
+
+    public ItemViewDelegate getItemViewDelegate(int viewType)
+    {
+        return delegates.get(viewType);
+    }
+
+    public int getItemViewLayoutId(int viewType)
+    {
+        return getItemViewDelegate(viewType).getItemViewLayoutId();
+    }
+
+    public int getItemViewType(ItemViewDelegate itemViewDelegate)
+    {
+        return delegates.indexOfValue(itemViewDelegate);
+    }
+}

+ 311 - 0
adapter/src/main/java/com/itant/library/recyclerview/base/ViewHolder.java

@@ -0,0 +1,311 @@
+package com.itant.library.recyclerview.base;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.text.util.Linkify;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RatingBar;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class ViewHolder extends RecyclerView.ViewHolder/* implements AnimateViewHolder*/
+{
+    private SparseArray<View> mViews;
+    private View mConvertView;
+    private Context mContext;
+
+    public ViewHolder(Context context, View itemView)
+    {
+        super(itemView);
+        mContext = context;
+        mConvertView = itemView;
+        mViews = new SparseArray<View>();
+    }
+
+
+    public static ViewHolder createViewHolder(Context context, View itemView)
+    {
+        ViewHolder holder = new ViewHolder(context, itemView);
+        return holder;
+    }
+
+    public static ViewHolder createViewHolder(Context context,
+                                              ViewGroup parent, int layoutId)
+    {
+        View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
+                false);
+        ViewHolder holder = new ViewHolder(context, itemView);
+        return holder;
+    }
+
+    /**
+     * 通过viewId获取控件
+     *
+     * @param viewId
+     * @return
+     */
+    public <T extends View> T getView(int viewId)
+    {
+        View view = mViews.get(viewId);
+        if (view == null)
+        {
+            view = mConvertView.findViewById(viewId);
+            mViews.put(viewId, view);
+        }
+        return (T) view;
+    }
+
+    public View getConvertView()
+    {
+        return mConvertView;
+    }
+
+
+
+
+    /****以下为辅助方法*****/
+
+    /**
+     * 设置TextView的值
+     *
+     * @param viewId
+     * @param text
+     * @return
+     */
+    public ViewHolder setText(int viewId, String text)
+    {
+        TextView tv = getView(viewId);
+        tv.setText(text);
+        return this;
+    }
+
+    public ViewHolder setImageResource(int viewId, int resId)
+    {
+        ImageView view = getView(viewId);
+        view.setImageResource(resId);
+        return this;
+    }
+
+    public ViewHolder setImageBitmap(int viewId, Bitmap bitmap)
+    {
+        ImageView view = getView(viewId);
+        view.setImageBitmap(bitmap);
+        return this;
+    }
+
+    public ViewHolder setImageDrawable(int viewId, Drawable drawable)
+    {
+        ImageView view = getView(viewId);
+        view.setImageDrawable(drawable);
+        return this;
+    }
+
+    public ViewHolder setBackgroundColor(int viewId, int color)
+    {
+        View view = getView(viewId);
+        view.setBackgroundColor(color);
+        return this;
+    }
+
+    public ViewHolder setBackgroundRes(int viewId, int backgroundRes)
+    {
+        View view = getView(viewId);
+        view.setBackgroundResource(backgroundRes);
+        return this;
+    }
+
+    public ViewHolder setTextColor(int viewId, int textColor)
+    {
+        TextView view = getView(viewId);
+        view.setTextColor(textColor);
+        return this;
+    }
+
+    public ViewHolder setTextColorRes(int viewId, int textColorRes)
+    {
+        TextView view = getView(viewId);
+        view.setTextColor(mContext.getResources().getColor(textColorRes));
+        return this;
+    }
+
+    @SuppressLint("NewApi")
+    public ViewHolder setAlpha(int viewId, float value)
+    {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
+        {
+            getView(viewId).setAlpha(value);
+        } else
+        {
+            // Pre-honeycomb hack to set Alpha value
+            AlphaAnimation alpha = new AlphaAnimation(value, value);
+            alpha.setDuration(0);
+            alpha.setFillAfter(true);
+            getView(viewId).startAnimation(alpha);
+        }
+        return this;
+    }
+
+    public ViewHolder setVisible(int viewId, boolean visible)
+    {
+        View view = getView(viewId);
+        view.setVisibility(visible ? View.VISIBLE : View.GONE);
+        return this;
+    }
+
+    public ViewHolder linkify(int viewId)
+    {
+        TextView view = getView(viewId);
+        Linkify.addLinks(view, Linkify.ALL);
+        return this;
+    }
+
+    public ViewHolder setTypeface(Typeface typeface, int... viewIds)
+    {
+        for (int viewId : viewIds)
+        {
+            TextView view = getView(viewId);
+            view.setTypeface(typeface);
+            view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
+        }
+        return this;
+    }
+
+    public ViewHolder setProgress(int viewId, int progress)
+    {
+        ProgressBar view = getView(viewId);
+        view.setProgress(progress);
+        return this;
+    }
+
+    public ViewHolder setProgress(int viewId, int progress, int max)
+    {
+        ProgressBar view = getView(viewId);
+        view.setMax(max);
+        view.setProgress(progress);
+        return this;
+    }
+
+    public ViewHolder setMax(int viewId, int max)
+    {
+        ProgressBar view = getView(viewId);
+        view.setMax(max);
+        return this;
+    }
+
+    public ViewHolder setRating(int viewId, float rating)
+    {
+        RatingBar view = getView(viewId);
+        LayerDrawable stars = (LayerDrawable) view.getProgressDrawable();
+        stars.getDrawable(2).setColorFilter(Color.parseColor("#eb002f"), PorterDuff.Mode.SRC_ATOP);
+        view.setRating(rating);
+        return this;
+    }
+
+    public ViewHolder setRating(int viewId, float rating, int max)
+    {
+        RatingBar view = getView(viewId);
+        view.setMax(max);
+        view.setRating(rating);
+        return this;
+    }
+
+    public ViewHolder setTag(int viewId, Object tag)
+    {
+        View view = getView(viewId);
+        view.setTag(tag);
+        return this;
+    }
+
+    public ViewHolder setTag(int viewId, int key, Object tag)
+    {
+        View view = getView(viewId);
+        view.setTag(key, tag);
+        return this;
+    }
+
+    public ViewHolder setChecked(int viewId, boolean checked)
+    {
+        Checkable view = (Checkable) getView(viewId);
+        view.setChecked(checked);
+        return this;
+    }
+
+    /**
+     * 关于事件的
+     */
+    public ViewHolder setOnClickListener(int viewId,
+                                         View.OnClickListener listener)
+    {
+        View view = getView(viewId);
+        view.setOnClickListener(listener);
+        return this;
+    }
+
+    public ViewHolder setOnTouchListener(int viewId,
+                                         View.OnTouchListener listener)
+    {
+        View view = getView(viewId);
+        view.setOnTouchListener(listener);
+        return this;
+    }
+
+    public ViewHolder setOnLongClickListener(int viewId,
+                                             View.OnLongClickListener listener)
+    {
+        View view = getView(viewId);
+        view.setOnLongClickListener(listener);
+        return this;
+    }
+
+
+    /*----------------------------------动画----------------------------------*/
+    /*@Override
+    public void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
+        ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
+        ViewCompat.setAlpha(itemView, 0);
+    }
+
+    @Override
+    public void preAnimateRemoveImpl(RecyclerView.ViewHolder holder) {
+
+    }
+
+    @Override
+    public void animateAddImpl(RecyclerView.ViewHolder holder, ViewPropertyAnimatorListener listener) {
+        // 添加的动画
+        ViewCompat.animate(itemView)
+                .translationY(0)
+                .alpha(1)
+                .setDuration(300)
+                .setListener(listener)
+                .start();
+    }
+
+    @Override
+    public void animateRemoveImpl(RecyclerView.ViewHolder holder, ViewPropertyAnimatorListener listener) {
+        // 移除的动画
+        ViewCompat.animate(itemView)
+                .translationY(-itemView.getHeight() * 0.3f)
+                .alpha(0)
+                .setDuration(300)
+                .setListener(listener)
+                .start();
+    }*/
+    /*----------------------------------动画----------------------------------*/
+}

+ 55 - 0
adapter/src/main/java/com/itant/library/recyclerview/utils/WrapperUtils.java

@@ -0,0 +1,55 @@
+package com.itant.library.recyclerview.utils;
+
+
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.StaggeredGridLayoutManager;
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public class WrapperUtils
+{
+    public interface SpanSizeCallback
+    {
+        int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position);
+    }
+
+    public static void onAttachedToRecyclerView(RecyclerView.Adapter innerAdapter, RecyclerView recyclerView, final SpanSizeCallback callback)
+    {
+        innerAdapter.onAttachedToRecyclerView(recyclerView);
+
+        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+        if (layoutManager instanceof GridLayoutManager)
+        {
+            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
+            final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
+
+            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
+            {
+                @Override
+                public int getSpanSize(int position)
+                {
+                    return callback.getSpanSize(gridLayoutManager, spanSizeLookup, position);
+                }
+            });
+            gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
+        }
+    }
+
+    public static void setFullSpan(RecyclerView.ViewHolder holder)
+    {
+        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+
+        if (lp != null
+                && lp instanceof StaggeredGridLayoutManager.LayoutParams)
+        {
+
+            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
+
+            p.setFullSpan(true);
+        }
+    }
+}

+ 128 - 0
adapter/src/main/java/com/itant/library/recyclerview/wrapper/EmptyWrapper.java

@@ -0,0 +1,128 @@
+package com.itant.library.recyclerview.wrapper;
+
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.itant.library.recyclerview.base.ViewHolder;
+import com.itant.library.recyclerview.utils.WrapperUtils;
+
+/**
+ * Created by iTant on 2017/1/15.
+ */
+public class EmptyWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
+{
+    public static final int ITEM_TYPE_EMPTY = Integer.MAX_VALUE - 1;
+
+    private RecyclerView.Adapter mInnerAdapter;
+    private View mEmptyView;
+    private int mEmptyLayoutId;
+
+
+    public EmptyWrapper(RecyclerView.Adapter adapter)
+    {
+        mInnerAdapter = adapter;
+    }
+
+    private boolean isEmpty()
+    {
+        return (mEmptyView != null || mEmptyLayoutId != 0) && mInnerAdapter.getItemCount() == 0;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
+    {
+        if (isEmpty())
+        {
+            ViewHolder holder;
+            if (mEmptyView != null)
+            {
+                holder = ViewHolder.createViewHolder(parent.getContext(), mEmptyView);
+            } else
+            {
+                holder = ViewHolder.createViewHolder(parent.getContext(), parent, mEmptyLayoutId);
+            }
+            return holder;
+        }
+        return mInnerAdapter.onCreateViewHolder(parent, viewType);
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(RecyclerView recyclerView)
+    {
+        WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback()
+        {
+            @Override
+            public int getSpanSize(GridLayoutManager gridLayoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position)
+            {
+                if (isEmpty())
+                {
+                    return gridLayoutManager.getSpanCount();
+                }
+                if (oldLookup != null)
+                {
+                    return oldLookup.getSpanSize(position);
+                }
+                return 1;
+            }
+        });
+
+
+    }
+
+    @Override
+    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
+    {
+        mInnerAdapter.onViewAttachedToWindow(holder);
+        if (isEmpty())
+        {
+            WrapperUtils.setFullSpan(holder);
+        }
+    }
+
+
+    @Override
+    public int getItemViewType(int position)
+    {
+        if (isEmpty())
+        {
+            return ITEM_TYPE_EMPTY;
+        }
+        return mInnerAdapter.getItemViewType(position);
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
+    {
+        if (isEmpty())
+        {
+            return;
+        }
+        mInnerAdapter.onBindViewHolder(holder, position);
+    }
+
+    @Override
+    public int getItemCount()
+    {
+        if (isEmpty()) {
+            return 1;
+        }
+        return mInnerAdapter.getItemCount();
+    }
+
+
+
+    public void setEmptyView(View emptyView)
+    {
+        mEmptyView = emptyView;
+    }
+
+    public void setEmptyView(int layoutId)
+    {
+        mEmptyLayoutId = layoutId;
+    }
+
+}

+ 152 - 0
adapter/src/main/java/com/itant/library/recyclerview/wrapper/HeaderAndFooterWrapper.java

@@ -0,0 +1,152 @@
+package com.itant.library.recyclerview.wrapper;
+
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.collection.SparseArrayCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.itant.library.recyclerview.base.ViewHolder;
+import com.itant.library.recyclerview.utils.WrapperUtils;
+
+
+public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
+{
+    private static final int BASE_ITEM_TYPE_HEADER = 100000;
+    private static final int BASE_ITEM_TYPE_FOOTER = 200000;
+
+    private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
+    private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
+
+    private RecyclerView.Adapter mInnerAdapter;
+
+    public HeaderAndFooterWrapper(RecyclerView.Adapter adapter)
+    {
+        mInnerAdapter = adapter;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
+    {
+        if (mHeaderViews.get(viewType) != null)
+        {
+            ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
+            return holder;
+
+        } else if (mFootViews.get(viewType) != null)
+        {
+            ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
+            return holder;
+        }
+        return mInnerAdapter.onCreateViewHolder(parent, viewType);
+    }
+
+    @Override
+    public int getItemViewType(int position)
+    {
+        if (isHeaderViewPos(position))
+        {
+            return mHeaderViews.keyAt(position);
+        } else if (isFooterViewPos(position))
+        {
+            return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
+        }
+        return mInnerAdapter.getItemViewType(position - getHeadersCount());
+    }
+
+    private int getRealItemCount()
+    {
+        return mInnerAdapter.getItemCount();
+    }
+
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
+    {
+        if (isHeaderViewPos(position))
+        {
+            return;
+        }
+        if (isFooterViewPos(position))
+        {
+            return;
+        }
+        mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
+    }
+
+    @Override
+    public int getItemCount()
+    {
+        return getHeadersCount() + getFootersCount() + getRealItemCount();
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(RecyclerView recyclerView)
+    {
+        WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback()
+        {
+            @Override
+            public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position)
+            {
+                int viewType = getItemViewType(position);
+                if (mHeaderViews.get(viewType) != null)
+                {
+                    return layoutManager.getSpanCount();
+                } else if (mFootViews.get(viewType) != null)
+                {
+                    return layoutManager.getSpanCount();
+                }
+                if (oldLookup != null) {
+                    return oldLookup.getSpanSize(position);
+                }
+                return 1;
+            }
+        });
+    }
+
+    @Override
+    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
+    {
+        mInnerAdapter.onViewAttachedToWindow(holder);
+        int position = holder.getLayoutPosition();
+        if (isHeaderViewPos(position) || isFooterViewPos(position))
+        {
+            WrapperUtils.setFullSpan(holder);
+        }
+    }
+
+    private boolean isHeaderViewPos(int position)
+    {
+        return position < getHeadersCount();
+    }
+
+    private boolean isFooterViewPos(int position)
+    {
+        return position >= getHeadersCount() + getRealItemCount();
+    }
+
+
+    public void addHeaderView(View view)
+    {
+        mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
+    }
+
+    public void addFootView(View view)
+    {
+        mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
+    }
+
+    public int getHeadersCount()
+    {
+        return mHeaderViews.size();
+    }
+
+    public int getFootersCount()
+    {
+        return mFootViews.size();
+    }
+
+
+}

+ 161 - 0
adapter/src/main/java/com/itant/library/recyclerview/wrapper/LoadMoreWrapper.java

@@ -0,0 +1,161 @@
+package com.itant.library.recyclerview.wrapper;
+
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.StaggeredGridLayoutManager;
+
+import com.itant.library.recyclerview.base.ViewHolder;
+import com.itant.library.recyclerview.utils.WrapperUtils;
+
+
+public class LoadMoreWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
+{
+    public static final int ITEM_TYPE_LOAD_MORE = Integer.MAX_VALUE - 2;
+
+    private RecyclerView.Adapter mInnerAdapter;
+    private View mLoadMoreView;
+    private int mLoadMoreLayoutId;
+
+    public LoadMoreWrapper(RecyclerView.Adapter adapter)
+    {
+        mInnerAdapter = adapter;
+    }
+
+    private boolean hasLoadMore()
+    {
+        return mLoadMoreView != null || mLoadMoreLayoutId != 0;
+    }
+
+
+    private boolean isShowLoadMore(int position)
+    {
+        return hasLoadMore() && (position >= mInnerAdapter.getItemCount());
+    }
+
+    @Override
+    public int getItemViewType(int position)
+    {
+        if (isShowLoadMore(position))
+        {
+            return ITEM_TYPE_LOAD_MORE;
+        }
+        return mInnerAdapter.getItemViewType(position);
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
+    {
+        if (viewType == ITEM_TYPE_LOAD_MORE)
+        {
+            ViewHolder holder;
+            if (mLoadMoreView != null)
+            {
+                holder = ViewHolder.createViewHolder(parent.getContext(), mLoadMoreView);
+            } else
+            {
+                holder = ViewHolder.createViewHolder(parent.getContext(), parent, mLoadMoreLayoutId);
+            }
+            return holder;
+        }
+        return mInnerAdapter.onCreateViewHolder(parent, viewType);
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
+    {
+        if (isShowLoadMore(position))
+        {
+            if (mOnLoadMoreListener != null)
+            {
+                mOnLoadMoreListener.onLoadMoreRequested();
+            }
+            return;
+        }
+        mInnerAdapter.onBindViewHolder(holder, position);
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(RecyclerView recyclerView)
+    {
+        WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback()
+        {
+            @Override
+            public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position)
+            {
+                if (isShowLoadMore(position))
+                {
+                    return layoutManager.getSpanCount();
+                }
+                if (oldLookup != null)
+                {
+                    return oldLookup.getSpanSize(position);
+                }
+                return 1;
+            }
+        });
+    }
+
+
+    @Override
+    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
+    {
+        mInnerAdapter.onViewAttachedToWindow(holder);
+
+        if (isShowLoadMore(holder.getLayoutPosition()))
+        {
+            setFullSpan(holder);
+        }
+    }
+
+    private void setFullSpan(RecyclerView.ViewHolder holder)
+    {
+        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+
+        if (lp != null
+                && lp instanceof StaggeredGridLayoutManager.LayoutParams)
+        {
+            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
+
+            p.setFullSpan(true);
+        }
+    }
+
+    @Override
+    public int getItemCount()
+    {
+        return mInnerAdapter.getItemCount() + (hasLoadMore() ? 1 : 0);
+    }
+
+
+    public interface OnLoadMoreListener
+    {
+        void onLoadMoreRequested();
+    }
+
+    private OnLoadMoreListener mOnLoadMoreListener;
+
+    public LoadMoreWrapper setOnLoadMoreListener(OnLoadMoreListener loadMoreListener)
+    {
+        if (loadMoreListener != null)
+        {
+            mOnLoadMoreListener = loadMoreListener;
+        }
+        return this;
+    }
+
+    public LoadMoreWrapper setLoadMoreView(View loadMoreView)
+    {
+        mLoadMoreView = loadMoreView;
+        return this;
+    }
+
+    public LoadMoreWrapper setLoadMoreView(int layoutId)
+    {
+        mLoadMoreLayoutId = layoutId;
+        return this;
+    }
+}

+ 13 - 0
adapter/src/main/res/anim/anim_bottom_in.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:duration="500">
+
+    <translate
+        android:fromYDelta="50%"
+        android:toYDelta="0%"/>
+
+    <alpha
+        android:fromAlpha="0.5"
+        android:toAlpha="1"/>
+
+</set>

+ 4 - 0
adapter/src/main/res/values-v21/dimen.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>

+ 11 - 0
adapter/src/main/res/values/color.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="color_primary_red">#f44336</color>
+    <color name="color_primary_red_dark">#d32f2f</color>
+    <color name="color_primary_green"> #4caf50</color>
+    <color name="color_primary_green_dark">#388e3c</color>
+    <color name="color_primary_blue">#2196f3</color>
+    <color name="color_primary_blue_dark">#1976d2</color>
+    <color name="color_accent_pink">#ff4081</color>
+    <color name="color_shadow">#44000000</color>
+</resources>

+ 5 - 0
adapter/src/main/res/values/dimen.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="tabsHeight">48dp</dimen>
+    <dimen name="fab_margin">0dp</dimen>
+</resources>

+ 3 - 0
adapter/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Library</string>
+</resources>

+ 22 - 0
adapter/src/main/res/values/styles.xml

@@ -0,0 +1,22 @@
+<resources>
+
+    <style name="CustomTheme" parent="Theme.AppCompat.Light.NoActionBar">
+    </style>
+
+    <style name="AppThemeRed" parent="CustomTheme">
+        <item name="colorPrimary">@color/color_primary_red</item>
+        <item name="colorPrimaryDark">@color/color_primary_red_dark</item>
+    </style>
+
+    <style name="AppThemeGreen" parent="CustomTheme">
+        <item name="colorPrimary">@color/color_primary_green</item>
+        <item name="colorPrimaryDark">@color/color_primary_green_dark</item>
+    </style>
+
+    <style name="AppThemeBlue" parent="CustomTheme">
+        <item name="colorPrimary">@color/color_primary_blue</item>
+        <item name="colorPrimaryDark">@color/color_primary_blue_dark</item>
+        <item name="colorAccent">@color/color_accent_pink</item>
+    </style>
+
+</resources>

+ 1 - 0
app/.gitignore

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

+ 42 - 0
app/build.gradle

@@ -0,0 +1,42 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion versions.compileSdk
+    buildToolsVersion versions.buildTools
+    defaultConfig {
+        applicationId "com.miekir.ocr"
+        minSdkVersion versions.minSdk
+        targetSdkVersion versions.targetSdk
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+    compile project(path: ':common')
+    implementation project(path: ':network')
+    implementation project(path: ':mvp')
+    implementation project(path: ':adapter')
+
+    //implementation 'com.github.tbruyelle:rxpermissions:0.12'
+    compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.8.1@aar'
+}

+ 21 - 0
app/proguard-rules.pro

@@ -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

+ 27 - 0
app/src/androidTest/java/com/miekir/ocr/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.miekir.ocr;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.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() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        assertEquals("com.miekir.ocr", appContext.getPackageName());
+    }
+}

+ 33 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.miekir.ocr">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-feature android:name="android.hardware.camera2.full" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        tools:ignore="GoogleAppIndexingWarning">
+        <activity android:name=".MainActivity"
+            android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".CameraActivity"
+            android:screenOrientation="portrait"/>
+    </application>
+
+</manifest>

+ 445 - 0
app/src/main/java/com/miekir/ocr/CameraActivity.java

@@ -0,0 +1,445 @@
+package com.miekir.ocr;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Size;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.miekir.ocr.base.BaseCameraActivity;
+import com.miekir.ocr.tool.Utils;
+import com.miekir.ocr.view.AutoFitTextureView;
+import com.tbruyelle.rxpermissions2.RxPermissions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import static android.os.Environment.DIRECTORY_PICTURES;
+
+public class CameraActivity extends BaseCameraActivity {
+
+    private static SparseIntArray ORIENTATIONS = new SparseIntArray();
+
+    static {
+        ORIENTATIONS.append(Surface.ROTATION_0, 90);
+        ORIENTATIONS.append(Surface.ROTATION_90, 0);
+        ORIENTATIONS.append(Surface.ROTATION_180, 270);
+        ORIENTATIONS.append(Surface.ROTATION_270, 180);
+    }
+
+    private String cameraId;
+    private CameraDevice cameraDevice;
+    private CameraCaptureSession cameraCaptureSessions;
+    private CaptureRequest.Builder captureRequestBuilder;
+    private Size imageDimension;
+    private ImageReader imageReader;
+
+    private File file = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES) + "/" + System.currentTimeMillis() + ".jpg");
+
+    private static final int REQUEST_CAMERA_PERMISSION = 200;
+
+    private Handler mBackgroundHandler;
+    private HandlerThread mBackgroundThread;
+
+    private AutoFitTextureView textureView;
+    private Button button;
+
+    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
+
+    private static final int MAX_PREVIEW_WIDTH = 1920;
+    private static final int MAX_PREVIEW_HEIGHT = 1080;
+    private int mSensorOrientation;
+
+    private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
+        @Override
+        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+            openCamera(width, height);
+        }
+
+        @Override
+        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+            if (null != textureView || null == imageDimension) {
+                textureView.setTransform(Utils.configureTransform(width, height, imageDimension, CameraActivity.this));
+            }
+        }
+
+        @Override
+        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+            return false;
+        }
+
+        @Override
+        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+        }
+    };
+
+    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
+        @Override
+        public void onOpened(CameraDevice camera) {
+
+            Log.e("tag", "onOpened");
+            mCameraOpenCloseLock.release();
+            cameraDevice = camera;
+            createCameraPreview();
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice camera) {
+            mCameraOpenCloseLock.release();
+            cameraDevice.close();
+        }
+
+        @Override
+        public void onError(CameraDevice camera, int error) {
+            mCameraOpenCloseLock.release();
+            cameraDevice.close();
+            cameraDevice = null;
+        }
+    };
+
+
+    @Override
+    public int getLayoutID() {
+        return R.layout.activity_camera;
+    }
+
+    @SuppressLint("CheckResult")
+    @Override
+    public void initViews(Bundle savedInstanceState) {
+        textureView = (AutoFitTextureView) findViewById(R.id.textureView);
+        button = (Button) findViewById(R.id.button);
+
+        textureView.setSurfaceTextureListener(textureListener);
+
+        button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                takePicture();
+            }
+        });
+    }
+
+    private void openCamera(int width, int height) {
+        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+        Log.e("tag", "is camera open");
+
+        try {
+            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+                throw new RuntimeException("Time out waiting to lock camera opening.");
+            }
+            cameraId = manager.getCameraIdList()[0];
+            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+            assert map != null;
+
+            Size largest = Collections.max(
+                    Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
+                    new Utils.CompareSizesByArea());
+
+
+            int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
+            //noinspection ConstantConditions
+            mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+            boolean swappedDimensions = false;
+            switch (displayRotation) {
+                case Surface.ROTATION_0:
+                case Surface.ROTATION_180:
+                    if (mSensorOrientation == 90 || mSensorOrientation == 270) {
+                        swappedDimensions = true;
+                    }
+                    break;
+                case Surface.ROTATION_90:
+                case Surface.ROTATION_270:
+                    if (mSensorOrientation == 0 || mSensorOrientation == 180) {
+                        swappedDimensions = true;
+                    }
+                    break;
+                default:
+                    Log.e("tag", "Display rotation is invalid: " + displayRotation);
+            }
+
+            Point displaySize = new Point();
+            getWindowManager().getDefaultDisplay().getSize(displaySize);
+            int rotatedPreviewWidth = width;
+            int rotatedPreviewHeight = height;
+            int maxPreviewWidth = displaySize.x;
+            int maxPreviewHeight = displaySize.y;
+
+            if (swappedDimensions) {
+                rotatedPreviewWidth = height;
+                rotatedPreviewHeight = width;
+                maxPreviewWidth = displaySize.y;
+                maxPreviewHeight = displaySize.x;
+            }
+
+            if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
+                maxPreviewWidth = MAX_PREVIEW_WIDTH;
+            }
+
+            if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
+                maxPreviewHeight = MAX_PREVIEW_HEIGHT;
+            }
+
+            imageDimension = Utils.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
+                    rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
+                    maxPreviewHeight, largest);
+
+            int orientation = getResources().getConfiguration().orientation;
+            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                textureView.setAspectRatio(
+                        imageDimension.getWidth(), imageDimension.getHeight());
+            } else {
+                textureView.setAspectRatio(
+                        imageDimension.getHeight(), imageDimension.getWidth());
+            }
+
+            if (null != textureView || null == imageDimension) {
+                textureView.setTransform(Utils.configureTransform(width, height, imageDimension, CameraActivity.this));
+            }
+
+            manager.openCamera(cameraId, stateCallback, null);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        Log.e("tag", "openCamera X");
+    }
+
+    protected void createCameraPreview() {
+        try {
+            SurfaceTexture texture = textureView.getSurfaceTexture();
+            assert texture != null;
+            texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());
+            Surface surface = new Surface(texture);
+            captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+            captureRequestBuilder.addTarget(surface);
+            cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
+                @Override
+                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
+                    //The camera is already closed
+                    if (null == cameraDevice) {
+                        return;
+                    }
+                    // When the session is ready, we start displaying the preview.
+                    cameraCaptureSessions = cameraCaptureSession;
+                    updatePreview();
+                }
+
+                @Override
+                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+                    Toast.makeText(CameraActivity.this, "Configuration change", Toast.LENGTH_SHORT).show();
+                }
+            }, null);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    protected void updatePreview() {
+        if (null == cameraDevice) {
+            Log.e("tag", "updatePreview error, return");
+        }
+        captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+        try {
+            cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    protected void startBackgroundThread() {
+        mBackgroundThread = new HandlerThread("Camera Background");
+        mBackgroundThread.start();
+        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+    }
+
+    protected void stopBackgroundThread() {
+        mBackgroundThread.quitSafely();
+        try {
+            mBackgroundThread.join();
+            mBackgroundThread = null;
+            mBackgroundHandler = null;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void closeCamera() {
+        try {
+            mCameraOpenCloseLock.acquire();
+            if (null != cameraCaptureSessions) {
+                cameraCaptureSessions.close();
+                cameraCaptureSessions = null;
+            }
+            if (null != cameraDevice) {
+                cameraDevice.close();
+                cameraDevice = null;
+            }
+            if (null != imageReader) {
+                imageReader.close();
+                imageReader = null;
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while trying to lock camera closing.");
+        } finally {
+            mCameraOpenCloseLock.release();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.e("tag", "onResume");
+        startBackgroundThread();
+        if (textureView.isAvailable()) {
+            openCamera(textureView.getWidth(), textureView.getHeight());
+        } else {
+            textureView.setSurfaceTextureListener(textureListener);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        Log.e("tag", "onPause");
+        closeCamera();
+        stopBackgroundThread();
+        super.onPause();
+    }
+
+    protected void takePicture() {
+        if (null == cameraDevice) {
+            Log.e("tag", "cameraDevice is null");
+            return;
+        }
+        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+        try {
+            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraDevice.getId());
+            Size[] jpegSizes = null;
+            if (characteristics != null) {
+                jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
+            }
+            int width = 640;
+            int height = 480;
+            if (jpegSizes != null && 0 < jpegSizes.length) {
+                width = jpegSizes[0].getWidth();
+                height = jpegSizes[0].getHeight();
+            }
+            ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
+            List<Surface> outputSurfaces = new ArrayList<Surface>(2);
+            outputSurfaces.add(reader.getSurface());
+            outputSurfaces.add(new Surface(textureView.getSurfaceTexture()));
+            final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            captureBuilder.addTarget(reader.getSurface());
+            captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+            // Orientation
+            int rotation = getWindowManager().getDefaultDisplay().getRotation();
+            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
+
+
+            reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);
+
+            final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
+                @Override
+                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
+                    super.onCaptureCompleted(session, request, result);
+
+                    Toast.makeText(CameraActivity.this, "Saved:" + file, Toast.LENGTH_SHORT).show();
+                    createCameraPreview();
+                }
+
+            };
+
+            cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
+                @Override
+                public void onConfigured(CameraCaptureSession session) {
+                    try {
+                        session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
+                    } catch (CameraAccessException e) {
+                        e.printStackTrace();
+                    }
+                }
+
+                @Override
+                public void onConfigureFailed(CameraCaptureSession session) {
+                }
+            }, mBackgroundHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+
+            Image image = null;
+            try {
+                image = reader.acquireLatestImage();
+                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
+                byte[] bytes = new byte[buffer.capacity()];
+                buffer.get(bytes);
+                save(bytes);
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                if (image != null) {
+                    image.close();
+                }
+            }
+        }
+
+        private void save(byte[] bytes) throws IOException {
+            OutputStream output = null;
+            try {
+                output = new FileOutputStream(file);
+                output.write(bytes);
+            } finally {
+                if (null != output) {
+                    output.close();
+                }
+            }
+        }
+    };
+}

+ 52 - 0
app/src/main/java/com/miekir/ocr/MainActivity.java

@@ -0,0 +1,52 @@
+package com.miekir.ocr;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.tbruyelle.rxpermissions2.RxPermissions;
+
+/**
+ * Copyright (C), 2019-2020, Genlot
+ *
+ * @author 詹子聪
+ * @date 2020/7/25 20:33
+ * Description: 主界面
+ */
+public class MainActivity extends AppCompatActivity {
+    @SuppressLint("CheckResult")
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        final RxPermissions rxPermissions = RxPermissions.getInstance(this);
+        rxPermissions
+                .request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                .subscribe(granted -> {
+                    if (granted) {
+                        // 打开相机
+                        startActivity(new Intent(MainActivity.this, CameraActivity.class));
+                        finish();
+                    } else {
+                        // 权限被拒绝
+                        showDenyDialog();
+                    }
+                });
+    }
+
+    protected void showDenyDialog() {
+        AlertDialog dialog = new AlertDialog
+                .Builder(this)
+                .setMessage("Camera or Storage Permissions Denied")
+                .setPositiveButton("Confirm", (dialogInterface, i) -> {
+                    finish();
+                }).create();
+        dialog.show();
+    }
+}

+ 17 - 0
app/src/main/java/com/miekir/ocr/base/BaseCameraActivity.java

@@ -0,0 +1,17 @@
+package com.miekir.ocr.base;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.miekir.mvp.view.BaseMVPActivity;
+
+/**
+ * Copyright (C), 2019-2020, Genlot
+ *
+ * @author 詹子聪
+ * @date 2020/7/25 20:18
+ * Description: 基类
+ */
+public abstract class BaseCameraActivity extends BaseMVPActivity {
+
+
+}

+ 127 - 0
app/src/main/java/com/miekir/ocr/tool/Utils.java

@@ -0,0 +1,127 @@
+package com.miekir.ocr.tool;
+
+import android.app.Activity;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.media.Image;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Created by kinjal.dhamat on 4/13/2017.
+ */
+
+public class Utils {
+
+    public static Matrix configureTransform(int viewWidth, int viewHeight, Size mPreviewSize, Activity context) {
+
+        int rotation = context.getWindowManager().getDefaultDisplay().getRotation();
+        Matrix matrix = new Matrix();
+        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
+        RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
+        float centerX = viewRect.centerX();
+        float centerY = viewRect.centerY();
+        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
+            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
+            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+            float scale = Math.max(
+                    (float) viewHeight / mPreviewSize.getHeight(),
+                    (float) viewWidth / mPreviewSize.getWidth());
+            matrix.postScale(scale, scale, centerX, centerY);
+            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
+        } else if (Surface.ROTATION_180 == rotation) {
+            matrix.postRotate(180, centerX, centerY);
+        }
+        return matrix;
+    }
+
+    public static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
+                                         int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
+
+        // Collect the supported resolutions that are at least as big as the preview Surface
+        List<Size> bigEnough = new ArrayList<>();
+        // Collect the supported resolutions that are smaller than the preview Surface
+        List<Size> notBigEnough = new ArrayList<>();
+        int w = aspectRatio.getWidth();
+        int h = aspectRatio.getHeight();
+        for (Size option : choices) {
+            if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
+                    option.getHeight() == option.getWidth() * h / w) {
+                if (option.getWidth() >= textureViewWidth &&
+                        option.getHeight() >= textureViewHeight) {
+                    bigEnough.add(option);
+                } else {
+                    notBigEnough.add(option);
+                }
+            }
+        }
+
+        if (bigEnough.size() > 0) {
+            return Collections.min(bigEnough, new Utils.CompareSizesByArea());
+        } else if (notBigEnough.size() > 0) {
+            return Collections.max(notBigEnough, new Utils.CompareSizesByArea());
+        } else {
+            Log.e("tag", "Couldn't find any suitable preview size");
+            return choices[0];
+        }
+    }
+
+    public static class CompareSizesByArea implements Comparator<Size> {
+
+        @Override
+        public int compare(Size lhs, Size rhs) {
+
+            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
+                    (long) rhs.getWidth() * rhs.getHeight());
+        }
+
+    }
+
+
+    public static class ImageSaver implements Runnable {
+
+        private final Image mImage;
+
+        private final File mFile;
+
+        public ImageSaver(Image image, File file) {
+            mImage = image;
+            mFile = file;
+        }
+
+        @Override
+        public void run() {
+            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
+            byte[] bytes = new byte[buffer.remaining()];
+            buffer.get(bytes);
+            FileOutputStream output = null;
+            try {
+                output = new FileOutputStream(mFile);
+                output.write(bytes);
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                mImage.close();
+                if (null != output) {
+                    try {
+                        output.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+    }
+
+}

+ 65 - 0
app/src/main/java/com/miekir/ocr/view/AutoFitTextureView.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.miekir.ocr.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+public class AutoFitTextureView extends TextureView {
+
+    private int mRatioWidth = 0;
+    private int mRatioHeight = 0;
+
+    public AutoFitTextureView(Context context) {
+        this(context, null);
+    }
+
+    public AutoFitTextureView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setAspectRatio(int width, int height) {
+        if (width < 0 || height < 0) {
+            throw new IllegalArgumentException("Size cannot be negative.");
+        }
+        mRatioWidth = width;
+        mRatioHeight = height;
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        if (0 == mRatioWidth || 0 == mRatioHeight) {
+            setMeasuredDimension(width, height);
+        } else {
+            if (width < height * mRatioWidth / mRatioHeight) {
+                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
+            } else {
+                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
+            }
+        }
+    }
+
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 34 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#008577"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 29 - 0
app/src/main/res/layout/activity_camera.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="56dp"
+        android:orientation="horizontal"
+        android:background="@color/colorPrimary">
+
+    </LinearLayout>
+
+
+    <com.miekir.ocr.view.AutoFitTextureView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:id="@+id/textureView" />
+
+    <Button
+        android:text="Button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/button" />
+</LinearLayout>

+ 9 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+
+</RelativeLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 7 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#12ABE3</color>
+    <color name="colorPrimaryDark">#12ABE3</color>
+    <color name="colorAccent">#D81B60</color>
+
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">OCR Demo</string>
+</resources>

+ 15 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,15 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+</resources>

+ 17 - 0
app/src/test/java/com/miekir/ocr/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.miekir.ocr;
+
+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() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 37 - 0
build.gradle

@@ -0,0 +1,37 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+        
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.2'
+        
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        maven { url 'https://jitpack.io' }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+
+// 统一编译版本
+ext.versions = [
+        // Project
+        minSdk              : 21,
+        compileSdk          : 29,
+        targetSdk           : 29,
+        buildTools          : '29.0.2',
+]

+ 1 - 0
common/.gitignore

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

+ 65 - 0
common/build.gradle

@@ -0,0 +1,65 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion versions.compileSdk
+    buildToolsVersion versions.buildTools
+
+    defaultConfig {
+        minSdkVersion versions.minSdk
+        targetSdkVersion versions.targetSdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles 'consumer-rules.pro'
+    }
+
+    buildTypes {
+        debug {
+            minifyEnabled false
+            buildConfigField("boolean", "IS_DEBUG", 'true')
+        }
+
+        release {
+            minifyEnabled false
+            buildConfigField("boolean", "IS_DEBUG", 'false')
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+    api "androidx.core:core:1.4.0-alpha01"
+    api 'androidx.appcompat:appcompat:1.3.0-alpha01'
+    api 'com.google.android.material:material:1.1.0'
+    api 'androidx.vectordrawable:vectordrawable:1.1.0'
+    api 'androidx.navigation:navigation-fragment:2.2.2'
+    api 'androidx.navigation:navigation-ui:2.2.2'
+    api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+
+    // 带行号的Log
+    api 'com.github.zhaokaiqiang.klog:library:1.6.0'
+
+    // 查看内存泄露
+    //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.1'
+
+    // 防止Retrofit内存泄露
+    api 'com.trello.rxlifecycle3:rxlifecycle:3.1.0'
+    api 'com.trello.rxlifecycle3:rxlifecycle-android:3.1.0'
+    api 'com.trello.rxlifecycle3:rxlifecycle-components:3.1.0'
+
+    // 漂亮的TabLayout
+    api 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar'
+
+    // Glide加载图片
+    api 'com.github.bumptech.glide:glide:4.11.0'
+    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
+
+}

+ 0 - 0
common/consumer-rules.pro


+ 21 - 0
common/proguard-rules.pro

@@ -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

+ 27 - 0
common/src/androidTest/java/com/genlot/common/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.genlot.common;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.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() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        assertEquals("com.genlot.common.test", appContext.getPackageName());
+    }
+}

+ 27 - 0
common/src/androidTest/java/com/miekir/common/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.miekir.common;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.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() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        assertEquals("com.genlot.common.test", appContext.getPackageName());
+    }
+}

+ 10 - 0
common/src/main/AndroidManifest.xml

@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.miekir.common" >
+    <application>
+        <!--authorities需要是唯一的,随着包名变化而变化,否则一个手机只能有一个项目引入这个库-->
+        <provider
+            android:name=".provider.CommonInstaller"
+            android:authorities="${applicationId}.common"
+            android:exported="false"/>
+    </application>
+</manifest>

+ 31 - 0
common/src/main/java/com/miekir/common/adapter/TabFragmentAdapter.java

@@ -0,0 +1,31 @@
+package com.miekir.common.adapter;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+
+import java.util.List;
+
+/**
+ * Created by Jason on 2018/8/20.
+ * 在Fragment里使用ViewPager的适配器
+ */
+
+public class TabFragmentAdapter extends FragmentPagerAdapter {
+
+    private List<Fragment> tabFragments;
+    public TabFragmentAdapter(FragmentManager fm, List<Fragment> fragments) {
+        super(fm);
+        this.tabFragments = fragments;
+    }
+
+    @Override
+    public Fragment getItem(int position) {
+        return tabFragments == null ? null : tabFragments.get(position);
+    }
+
+    @Override
+    public int getCount() {
+        return tabFragments == null ? 0 : tabFragments.size();
+    }
+}

+ 59 - 0
common/src/main/java/com/miekir/common/provider/CommonInstaller.java

@@ -0,0 +1,59 @@
+package com.miekir.common.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.miekir.common.utils.SizeTool;
+import com.miekir.common.utils.ToastTool;
+
+/**
+ *
+ * @author 詹子聪
+ * @date 2020/7/5 0:17
+ * Description: 免手动初始化
+ */
+public class CommonInstaller extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        if (getContext() != null) {
+            ToastTool.getInstance().initTool(getContext().getApplicationContext());
+            SizeTool.SCREEN_WIDTH = getContext().getResources().getDisplayMetrics().widthPixels;
+            SizeTool.SCREEN_HEIGHT = getContext().getResources().getDisplayMetrics().heightPixels;
+        }
+
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
+        return 0;
+    }
+}

+ 30 - 0
common/src/main/java/com/miekir/common/utils/ActivityTool.java

@@ -0,0 +1,30 @@
+package com.miekir.common.utils;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ *
+ *
+ * @author 詹子聪
+ * @date 2020/6/21 21:32
+ * Description: 关于Activity的工具
+ */
+public class ActivityTool {
+    private ActivityTool() {}
+
+    public static void openUrl(Activity activity, String webUrl) {
+        Intent intent = new Intent();
+        intent.setAction("android.intent.action.VIEW");
+        intent.addCategory("android.intent.category.DEFAULT");
+        Uri uri = Uri.parse(webUrl);
+        intent.setData(uri);
+        try {
+            activity.startActivity(intent);
+        } catch (Exception e) {
+            ToastTool.showShort("找不到默认浏览器");
+            e.printStackTrace();
+        }
+    }
+}

+ 16 - 0
common/src/main/java/com/miekir/common/utils/SizeTool.java

@@ -0,0 +1,16 @@
+package com.miekir.common.utils;
+
+/**
+ *
+ *
+ * @author 詹子聪
+ * @date 2020/7/5 11:35
+ * Description: 尺寸工具
+ */
+public class SizeTool {
+    private SizeTool() {}
+
+    public static int SCREEN_WIDTH;
+    public static int SCREEN_HEIGHT;
+
+}

+ 68 - 0
common/src/main/java/com/miekir/common/utils/ToastTool.java

@@ -0,0 +1,68 @@
+package com.miekir.common.utils;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @author 詹子聪
+ * @date 2020/7/5 0:08
+ * Description: Toast工具
+ */
+public class ToastTool {
+    private static final long PERIOD_SHORT = 1500L;
+    private static final long PERIOD_LONG = 2500L;
+
+    private static WeakReference<Context> contextWeakReference = new WeakReference<>(null);
+    private ToastTool() { }
+    private static ToastTool instance;
+    public void initTool(Context context) {
+        contextWeakReference = new WeakReference<>(context);
+    }
+
+    public static ToastTool getInstance() {
+        if (instance == null) {
+            init();
+        }
+        return instance;
+    }
+
+    private static synchronized void init() {
+        if (instance == null) {
+            instance = new ToastTool();
+        }
+    }
+
+    private static long mLastShortToastMillis;
+    private static long mLastLongToastMillis;
+    /**
+     * @param text 要弹出的语句
+     */
+    public static void showShort(String text) {
+        Context context = contextWeakReference.get();
+        if (context == null) {
+            return;
+        }
+
+        if (System.currentTimeMillis() - mLastShortToastMillis > PERIOD_SHORT) {
+            mLastShortToastMillis = System.currentTimeMillis();
+            Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    /**
+     * @param text 要弹出的语句
+     */
+    public static void showLong(String text) {
+        Context context = contextWeakReference.get();
+        if (context == null) {
+            return;
+        }
+
+        if (System.currentTimeMillis() - mLastLongToastMillis > PERIOD_LONG) {
+            mLastLongToastMillis = System.currentTimeMillis();
+            Toast.makeText(context, text, Toast.LENGTH_LONG).show();
+        }
+    }
+}

+ 32 - 0
common/src/main/java/com/miekir/common/utils/ViewTool.java

@@ -0,0 +1,32 @@
+package com.miekir.common.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+
+/**
+ *
+ *
+ * @author 詹子聪
+ * @date 2020/7/5 8:21
+ * Description: View相关的工具
+ */
+public class ViewTool {
+    private ViewTool() {}
+
+    /**
+     * 批量设置监听
+     * @param activity 控件所在Activity
+     * @param viewIdArray 需要监听的控件ID列表
+     * @param listener 监听回调
+     */
+    public static void setOnClickListener(Activity activity, int[] viewIdArray, View.OnClickListener listener) {
+        if (activity == null || viewIdArray == null || listener == null || viewIdArray.length == 0) {
+            return;
+        }
+
+        for (int viewId : viewIdArray) {
+            activity.findViewById(viewId).setOnClickListener(listener);
+        }
+    }
+}

+ 141 - 0
common/src/main/java/com/miekir/common/view/AlignTextView.java

@@ -0,0 +1,141 @@
+package com.miekir.common.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.text.Layout;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+
+import androidx.appcompat.widget.AppCompatTextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 两端对齐的textview,可以设置最后一行靠左,靠右,居中对齐
+ *
+ * @author yuyh.
+ * @date 16/4/10.
+ */
+public class AlignTextView extends AppCompatTextView {
+    private float textHeight; // 单行文字高度
+    private int width; // textview宽度
+    private List<String> lines = new ArrayList<String>(); // 分割后的行
+    private List<Integer> tailLines = new ArrayList<Integer>(); // 尾行
+    private Align align = Align.ALIGN_LEFT; // 默认最后一行左对齐
+
+    // 尾行对齐方式,针对段落最后一行
+    public enum Align {
+        ALIGN_LEFT,
+        ALIGN_CENTER,
+        ALIGN_RIGHT,
+    }
+
+    public AlignTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        TextPaint paint = getPaint();
+        paint.setColor(getCurrentTextColor());
+        paint.drawableState = getDrawableState();
+        width = getMeasuredWidth();
+        String text = getText().toString();
+
+        Paint.FontMetrics fm = paint.getFontMetrics();
+        // 计算行高
+        Layout layout = getLayout();
+
+        // layout.getLayout()在4.4.3出现NullPointerException
+        if (layout == null) {
+            return;
+        }
+
+        textHeight = fm.descent - fm.ascent;
+        textHeight = textHeight * layout.getSpacingMultiplier() + layout.getSpacingAdd();
+
+        float firstHeight = getTextSize();
+
+        int gravity = getGravity();
+        if ((gravity & 0x1000) == 0) { // 是否垂直居中
+            firstHeight = firstHeight + (textHeight - firstHeight) / 2;
+        }
+
+        int paddingLeft = getPaddingLeft();
+        int paddingRight = getPaddingRight();
+        width = width - paddingLeft - paddingRight;
+
+        lines.clear();
+        tailLines.clear();
+
+        // 文本含有换行符时,分割单独处理
+        String[] items = text.split("\\n");
+        for (String item : items) {
+            calc(paint, item);
+        }
+
+        for (int i = 0; i < lines.size(); i++) {
+            float drawY = i * textHeight + firstHeight;
+            String line = lines.get(i);
+            // 绘画起始x坐标
+            float drawSpacingX = paddingLeft;
+            float gap = (width - paint.measureText(line));
+            float interval = gap / (line.length() - 1);
+
+            // 绘制最后一行
+            if (tailLines.contains(i)) {
+                interval = 0;
+                if (align == Align.ALIGN_CENTER)
+                    drawSpacingX += gap / 2;
+                else if (align == Align.ALIGN_RIGHT)
+                    drawSpacingX += gap;
+            }
+
+            for (int j = 0; j < line.length(); j++) {
+                float drawX = paint.measureText(line.substring(0, j))
+                        + interval * j;
+                canvas.drawText(line.substring(j, j + 1), drawX + drawSpacingX,
+                        drawY, paint);
+            }
+        }
+    }
+
+    /**
+     * 设置尾行对齐方式
+     *
+     * @param align
+     */
+    public void setAlign(Align align) {
+        this.align = align;
+        invalidate();
+    }
+
+    /**
+     * 计算每行应显示的文本数
+     *
+     * @param text
+     * @return
+     */
+    private void calc(Paint paint, String text) {
+        if (text.length() == 0) {
+            lines.add("\n");
+            return;
+        }
+        StringBuffer sb = new StringBuffer("");
+        int startPosition = 0; // 起始位置
+        for (int i = 0; i < text.length(); i++) {
+            if (paint.measureText(text.substring(startPosition, i + 1)) > width) {
+                startPosition = i;
+                lines.add(sb.toString());
+                sb = new StringBuffer();
+            }
+            sb.append(text.charAt(i));
+        }
+        if (sb.length() > 0)
+            lines.add(sb.toString());
+
+        tailLines.add(lines.size() - 1);
+    }
+}

+ 237 - 0
common/src/main/java/com/miekir/common/view/RoundedCornersTransformation.java

@@ -0,0 +1,237 @@
+package com.miekir.common.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+
+import java.security.MessageDigest;
+
+public class RoundedCornersTransformation extends BitmapTransformation {
+
+  private static final int VERSION = 1;
+  private static final String ID = "jp.wasabeef.glide.transformations.RoundedCornersTransformation." + VERSION;
+
+  public enum CornerType {
+    ALL,
+    TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,
+    TOP, BOTTOM, LEFT, RIGHT,
+    OTHER_TOP_LEFT, OTHER_TOP_RIGHT, OTHER_BOTTOM_LEFT, OTHER_BOTTOM_RIGHT,
+    DIAGONAL_FROM_TOP_LEFT, DIAGONAL_FROM_TOP_RIGHT
+  }
+
+  private int radius;
+  private int diameter;
+  private int margin;
+  private CornerType cornerType;
+
+  public RoundedCornersTransformation(int radius, int margin) {
+    this(radius, margin, CornerType.ALL);
+  }
+
+  public RoundedCornersTransformation(int radius, int margin, CornerType cornerType) {
+    this.radius = radius;
+    this.diameter = this.radius * 2;
+    this.margin = margin;
+    this.cornerType = cornerType;
+  }
+
+  @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+    int width = toTransform.getWidth();
+    int height = toTransform.getHeight();
+
+    Bitmap bitmap = pool.get(width, height, Bitmap.Config.ARGB_8888);
+    bitmap.setHasAlpha(true);
+
+    Canvas canvas = new Canvas(bitmap);
+    Paint paint = new Paint();
+    paint.setAntiAlias(true);
+    paint.setShader(new BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+    drawRoundRect(canvas, paint, width, height);
+    return bitmap;
+  }
+
+  private void drawRoundRect(Canvas canvas, Paint paint, float width, float height) {
+    float right = width - margin;
+    float bottom = height - margin;
+
+    switch (cornerType) {
+      case ALL:
+        canvas.drawRoundRect(new RectF(margin, margin, right, bottom), radius, radius, paint);
+        break;
+      case TOP_LEFT:
+        drawTopLeftRoundRect(canvas, paint, right, bottom);
+        break;
+      case TOP_RIGHT:
+        drawTopRightRoundRect(canvas, paint, right, bottom);
+        break;
+      case BOTTOM_LEFT:
+        drawBottomLeftRoundRect(canvas, paint, right, bottom);
+        break;
+      case BOTTOM_RIGHT:
+        drawBottomRightRoundRect(canvas, paint, right, bottom);
+        break;
+      case TOP:
+        drawTopRoundRect(canvas, paint, right, bottom);
+        break;
+      case BOTTOM:
+        drawBottomRoundRect(canvas, paint, right, bottom);
+        break;
+      case LEFT:
+        drawLeftRoundRect(canvas, paint, right, bottom);
+        break;
+      case RIGHT:
+        drawRightRoundRect(canvas, paint, right, bottom);
+        break;
+      case OTHER_TOP_LEFT:
+        drawOtherTopLeftRoundRect(canvas, paint, right, bottom);
+        break;
+      case OTHER_TOP_RIGHT:
+        drawOtherTopRightRoundRect(canvas, paint, right, bottom);
+        break;
+      case OTHER_BOTTOM_LEFT:
+        drawOtherBottomLeftRoundRect(canvas, paint, right, bottom);
+        break;
+      case OTHER_BOTTOM_RIGHT:
+        drawOtherBottomRightRoundRect(canvas, paint, right, bottom);
+        break;
+      case DIAGONAL_FROM_TOP_LEFT:
+        drawDiagonalFromTopLeftRoundRect(canvas, paint, right, bottom);
+        break;
+      case DIAGONAL_FROM_TOP_RIGHT:
+        drawDiagonalFromTopRightRoundRect(canvas, paint, right, bottom);
+        break;
+      default:
+        canvas.drawRoundRect(new RectF(margin, margin, right, bottom), radius, radius, paint);
+        break;
+    }
+  }
+
+  private void drawTopLeftRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, margin + diameter, margin + diameter), radius,
+        radius, paint);
+    canvas.drawRect(new RectF(margin, margin + radius, margin + radius, bottom), paint);
+    canvas.drawRect(new RectF(margin + radius, margin, right, bottom), paint);
+  }
+
+  private void drawTopRightRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(right - diameter, margin, right, margin + diameter), radius,
+        radius, paint);
+    canvas.drawRect(new RectF(margin, margin, right - radius, bottom), paint);
+    canvas.drawRect(new RectF(right - radius, margin + radius, right, bottom), paint);
+  }
+
+  private void drawBottomLeftRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, bottom - diameter, margin + diameter, bottom), radius,
+        radius, paint);
+    canvas.drawRect(new RectF(margin, margin, margin + diameter, bottom - radius), paint);
+    canvas.drawRect(new RectF(margin + radius, margin, right, bottom), paint);
+  }
+
+  private void drawBottomRightRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(right - diameter, bottom - diameter, right, bottom), radius,
+        radius, paint);
+    canvas.drawRect(new RectF(margin, margin, right - radius, bottom), paint);
+    canvas.drawRect(new RectF(right - radius, margin, right, bottom - radius), paint);
+  }
+
+  private void drawTopRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, right, margin + diameter), radius, radius,
+        paint);
+    canvas.drawRect(new RectF(margin, margin + radius, right, bottom), paint);
+  }
+
+  private void drawBottomRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, bottom - diameter, right, bottom), radius, radius,
+        paint);
+    canvas.drawRect(new RectF(margin, margin, right, bottom - radius), paint);
+  }
+
+  private void drawLeftRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, margin + diameter, bottom), radius, radius,
+        paint);
+    canvas.drawRect(new RectF(margin + radius, margin, right, bottom), paint);
+  }
+
+  private void drawRightRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(right - diameter, margin, right, bottom), radius, radius, paint);
+    canvas.drawRect(new RectF(margin, margin, right - radius, bottom), paint);
+  }
+
+  private void drawOtherTopLeftRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, bottom - diameter, right, bottom), radius, radius,
+        paint);
+    canvas.drawRoundRect(new RectF(right - diameter, margin, right, bottom), radius, radius, paint);
+    canvas.drawRect(new RectF(margin, margin, right - radius, bottom - radius), paint);
+  }
+
+  private void drawOtherTopRightRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, margin + diameter, bottom), radius, radius,
+        paint);
+    canvas.drawRoundRect(new RectF(margin, bottom - diameter, right, bottom), radius, radius,
+        paint);
+    canvas.drawRect(new RectF(margin + radius, margin, right, bottom - radius), paint);
+  }
+
+  private void drawOtherBottomLeftRoundRect(Canvas canvas, Paint paint, float right, float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, right, margin + diameter), radius, radius,
+        paint);
+    canvas.drawRoundRect(new RectF(right - diameter, margin, right, bottom), radius, radius, paint);
+    canvas.drawRect(new RectF(margin, margin + radius, right - radius, bottom), paint);
+  }
+
+  private void drawOtherBottomRightRoundRect(Canvas canvas, Paint paint, float right,
+      float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, right, margin + diameter), radius, radius,
+        paint);
+    canvas.drawRoundRect(new RectF(margin, margin, margin + diameter, bottom), radius, radius,
+        paint);
+    canvas.drawRect(new RectF(margin + radius, margin + radius, right, bottom), paint);
+  }
+
+  private void drawDiagonalFromTopLeftRoundRect(Canvas canvas, Paint paint, float right,
+      float bottom) {
+    canvas.drawRoundRect(new RectF(margin, margin, margin + diameter, margin + diameter), radius,
+        radius, paint);
+    canvas.drawRoundRect(new RectF(right - diameter, bottom - diameter, right, bottom), radius,
+        radius, paint);
+    canvas.drawRect(new RectF(margin, margin + radius, right - diameter, bottom), paint);
+    canvas.drawRect(new RectF(margin + diameter, margin, right, bottom - radius), paint);
+  }
+
+  private void drawDiagonalFromTopRightRoundRect(Canvas canvas, Paint paint, float right,
+      float bottom) {
+    canvas.drawRoundRect(new RectF(right - diameter, margin, right, margin + diameter), radius,
+        radius, paint);
+    canvas.drawRoundRect(new RectF(margin, bottom - diameter, margin + diameter, bottom), radius,
+        radius, paint);
+    canvas.drawRect(new RectF(margin, margin, right - radius, bottom - radius), paint);
+    canvas.drawRect(new RectF(margin + radius, margin + radius, right, bottom), paint);
+  }
+
+  @Override public String toString() {
+    return "RoundedTransformation(radius=" + radius + ", margin=" + margin + ", diameter="
+        + diameter + ", cornerType=" + cornerType.name() + ")";
+  }
+
+  @Override public boolean equals(Object o) {
+    return o instanceof RoundedCornersTransformation &&
+        ((RoundedCornersTransformation) o).radius == radius &&
+        ((RoundedCornersTransformation) o).diameter == diameter &&
+        ((RoundedCornersTransformation) o).margin == margin &&
+        ((RoundedCornersTransformation) o).cornerType == cornerType;
+  }
+
+  @Override public int hashCode() {
+    return ID.hashCode() + radius * 10000 + diameter * 1000 + margin * 100 + cornerType.ordinal() * 10;
+  }
+
+  @Override public void updateDiskCacheKey(MessageDigest messageDigest) {
+    messageDigest.update((ID + radius + diameter + margin + cornerType).getBytes(CHARSET));
+  }
+}

+ 45 - 0
common/src/main/java/com/miekir/common/widget/ScrollableViewPager.java

@@ -0,0 +1,45 @@
+package com.miekir.common.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.viewpager.widget.ViewPager;
+
+public class ScrollableViewPager extends ViewPager {
+
+    private boolean scrollable = false;
+
+
+    public ScrollableViewPager(Context context) {
+        super(context);
+    }
+
+    public ScrollableViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!scrollable) {
+            return false;
+        }
+        return super.onTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (!scrollable) {
+            return false;
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    public boolean isCanScrollble() {
+        return scrollable;
+    }
+
+    public void setCanScrollble(boolean scrollble) {
+        this.scrollable = scrollble;
+    }
+}  

+ 3 - 0
common/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">common</string>
+</resources>

+ 17 - 0
common/src/test/java/com/genlot/common/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.genlot.common;
+
+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() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 17 - 0
common/src/test/java/com/miekir/common/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.miekir.common;
+
+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() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 20 - 0
gradle.properties

@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Sat Jul 25 09:19:02 CST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 10 - 0
local.properties

@@ -0,0 +1,10 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file should *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=E\:\\software\\Android\\newsdk

+ 1 - 0
mvp/.gitignore

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

+ 34 - 0
mvp/build.gradle

@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion versions.compileSdk
+    buildToolsVersion versions.buildTools
+
+    defaultConfig {
+        minSdkVersion versions.minSdk
+        targetSdkVersion versions.targetSdk
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles 'consumer-rules.pro'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+    implementation project(path: ':common')
+}

+ 0 - 0
mvp/consumer-rules.pro


+ 21 - 0
mvp/proguard-rules.pro

@@ -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

+ 27 - 0
mvp/src/androidTest/java/com/miekir/mvp/ExampleInstrumentedTest.java

@@ -0,0 +1,27 @@
+package com.miekir.mvp;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.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() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        assertEquals("com.miekir.mvp.test", appContext.getPackageName());
+    }
+}

+ 2 - 0
mvp/src/main/AndroidManifest.xml

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

+ 139 - 0
mvp/src/main/java/com/miekir/mvp/base/BaseActivity.java

@@ -0,0 +1,139 @@
+package com.miekir.mvp.base;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Looper;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.miekir.common.utils.ToastTool;
+import com.miekir.mvp.widget.LoadingDialog;
+
+/**
+ * 适配器模式,这个类会适配子类{@link com.miekir.mvp.view.BaseMVPActivity}的功能,帮子类实现具体的弹出加载框、弹出提示等基本操作
+ */
+public abstract class BaseActivity extends AppCompatActivity {
+    private LoadingDialog mLoadingDialog;
+    private View rootView;
+    //private Unbinder mBinder;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // 状态栏深色模式,改变状态栏文字颜色
+//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+//            getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+//        }
+
+        super.onCreate(savedInstanceState);
+        rootView = LayoutInflater.from(this).inflate(getLayoutID(), null);
+        setContentView(rootView);
+
+        View.OnTouchListener rootTouchListener = new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                // 点击空白处隐藏输入法(要配合Activity根布局增加以下标志使用
+                // android:focusable="true"
+                // android:focusableInTouchMode="true")
+                hideInputMethod();
+                return false;
+            }
+        };
+        rootView.setOnTouchListener(rootTouchListener);
+
+        //进入页面隐藏输入框
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+        //mBinder = ButterKnife.bind(this);
+        mLoadingDialog = new LoadingDialog(this);
+        initViews(savedInstanceState);
+    }
+
+    protected void hideInputMethod() {
+        if (rootView != null) {
+            rootView.requestFocus();
+        }
+
+        final InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null) {
+            return;
+        }
+
+        final View currentFocusView = getCurrentFocus();
+        if (currentFocusView != null) {
+            imm.hideSoftInputFromWindow(currentFocusView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        // 必须要在onPause隐藏键盘,在onDestroy就太晚了
+        hideInputMethod();
+        super.onPause();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        //mBinder.unbind();
+        dismissLoading();
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+    }
+
+    public abstract int getLayoutID();
+
+    public abstract void initViews(Bundle savedInstanceState);
+
+    private boolean isMainThread() {
+        return Looper.myLooper() == Looper.getMainLooper();
+    }
+
+    public void showLoading() {
+        if (isMainThread()) {
+            mLoadingDialog.show();
+        } else {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mLoadingDialog.show();
+                }
+            });
+        }
+    }
+
+    public void showLoading(final String msg) {
+        if (isMainThread()) {
+            mLoadingDialog.show(msg);
+        } else {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mLoadingDialog.show(msg);
+                }
+            });
+        }
+    }
+
+    public void dismissLoading() {
+        if (mLoadingDialog != null) {
+            if (isMainThread()) {
+                mLoadingDialog.close();
+            } else {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mLoadingDialog.close();
+                    }
+                });
+            }
+        }
+    }
+
+
+    public void showToast(String message) {
+        ToastTool.showShort(message);
+    }
+}

+ 106 - 0
mvp/src/main/java/com/miekir/mvp/base/BaseFragment.java

@@ -0,0 +1,106 @@
+package com.miekir.mvp.base;
+
+import android.os.Bundle;
+import android.os.Looper;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.miekir.mvp.widget.LoadingDialog;
+
+
+public abstract class BaseFragment extends Fragment {
+    public FragmentActivity activity;
+    //private Unbinder mBinder;
+    private LoadingDialog mLoadingDialog;
+    protected View rootView;
+
+    /**
+     * 设置布局layout
+     *
+     * @return 布局文件id
+     */
+    public abstract @LayoutRes
+    int getLayoutResId();
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        rootView = inflater.inflate(getLayoutResId(), container, false);
+        activity = getActivity();
+        return rootView;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        //mBinder = ButterKnife.bind(this, view);
+        mLoadingDialog = new LoadingDialog(getActivity());
+        onCreateViewFinished(savedInstanceState);
+    }
+
+    public abstract void onCreateViewFinished(Bundle state);
+
+
+    private boolean isMainThread() {
+        return Looper.myLooper() == Looper.getMainLooper();
+    }
+
+    public void showLoading() {
+        if (isMainThread()) {
+            mLoadingDialog.show();
+        } else {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mLoadingDialog.show();
+                }
+            });
+        }
+    }
+
+    public void showLoading(final String msg) {
+        if (isMainThread()) {
+            mLoadingDialog.show(msg);
+        } else {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mLoadingDialog.show(msg);
+                }
+            });
+        }
+    }
+
+    public void dismissLoading() {
+        if (mLoadingDialog != null) {
+            if (isMainThread()) {
+                mLoadingDialog.close();
+            } else {
+                activity.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mLoadingDialog.close();
+                    }
+                });
+            }
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        //mBinder.unbind();
+        dismissLoading();
+        activity = null;
+    }
+
+
+}

+ 24 - 0
mvp/src/main/java/com/miekir/mvp/presenter/BasePresenter.java

@@ -0,0 +1,24 @@
+package com.miekir.mvp.presenter;
+
+import com.miekir.mvp.view.IView;
+
+import java.lang.ref.WeakReference;
+
+public abstract class BasePresenter<V extends IView> {
+    private WeakReference<V> wrf;
+
+    public void attachView(V view) {
+        wrf = new WeakReference<V>(view);
+    }
+
+    public V getView() {
+        return wrf == null ? null : wrf.get();
+    }
+
+    public void detachView() {
+        if (wrf != null) {
+            wrf.clear();
+            wrf = null;
+        }
+    }
+}

+ 15 - 0
mvp/src/main/java/com/miekir/mvp/presenter/InjectPresenter.java

@@ -0,0 +1,15 @@
+package com.miekir.mvp.presenter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @description Presenter注入标记
+ * @date 2019/8/19
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface InjectPresenter {
+}

+ 72 - 0
mvp/src/main/java/com/miekir/mvp/view/BaseMVPActivity.java

@@ -0,0 +1,72 @@
+package com.miekir.mvp.view;
+
+import android.os.Bundle;
+
+import com.miekir.common.utils.ToastTool;
+import com.miekir.mvp.base.BaseActivity;
+import com.miekir.mvp.presenter.BasePresenter;
+import com.miekir.mvp.presenter.InjectPresenter;
+import com.miekir.mvp.view.IView;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 利用反射,BaseMVPActivity将会查找子类中有{@link InjectPresenter}注解的成员变量,
+ * 筛选出父类是{@link BasePresenter}的成员变量,对它们进行实例化,此时BaseMVPActivity的子类即拥有了
+ * 具体Presenter的引用,如拥有了LoginPresenter的引用,可以调用具体Presenter的具体方法;
+ * 然后BaseMVPActivity调用{@link BasePresenter}的一个方法把自身的{@link IView}类型引用
+ * 传递给{@link BasePresenter},这样{@link BasePresenter}的子类也就拥有了{@link IView}类型引用;
+ * 注意:因为注解是在BaseMVPActivity的子类声明的,子类实现了{@link IView}的子类如ILoginView,
+ * 这个子类是一个拥有特殊回调接口的,如拥有登录成功、登录失败等接口,
+ * 也就是说把具体的子类传this引用的时候,把具体的ILoginView引用传给了具体的Presenter,并通过{@link BasePresenter}
+ * 的泛型让getView方法返回具体的ILoginView,所以{@link BasePresenter}的子类还可以访问到ILoginView的特殊方法
+ */
+public abstract class BaseMVPActivity extends BaseActivity implements IView {
+
+    private List<BasePresenter> mInjectPresenters;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mInjectPresenters = new ArrayList<>();
+
+        // 这里可以获取到子类的成员变量
+        Field[] fields = this.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            //获取变量上面的注解类型
+            InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
+            if (injectPresenter != null) {
+                try {
+                    Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
+                    BasePresenter mInjectPresenter = type.newInstance();
+                    mInjectPresenter.attachView(this);
+                    field.setAccessible(true);
+                    field.set(this, mInjectPresenter);
+                    mInjectPresenters.add(mInjectPresenter);
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                } catch (InstantiationException e) {
+                    e.printStackTrace();
+                }catch (ClassCastException e){
+                    e.printStackTrace();
+                    throw new RuntimeException("SubClass must extends Class:BasePresenter");
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        for (BasePresenter presenter : mInjectPresenters) {
+            presenter.detachView();
+        }
+        mInjectPresenters.clear();
+        mInjectPresenters = null;
+    }
+
+
+
+}

+ 65 - 0
mvp/src/main/java/com/miekir/mvp/view/BaseMVPFragment.java

@@ -0,0 +1,65 @@
+package com.miekir.mvp.view;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.miekir.common.utils.ToastTool;
+import com.miekir.mvp.base.BaseFragment;
+import com.miekir.mvp.presenter.BasePresenter;
+import com.miekir.mvp.presenter.InjectPresenter;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public abstract class BaseMVPFragment extends BaseFragment implements IView{
+
+    private List<BasePresenter> mInjectPresenters;
+
+    @Override
+    public  void onCreateViewFinished(@Nullable Bundle savedInstanceState) {
+        mInjectPresenters = new ArrayList<>();
+        Field[] fields = this.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            //获取变量上面的注解类型
+            InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
+            if (injectPresenter != null) {
+                try {
+                    Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
+                    BasePresenter mInjectPresenter = type.newInstance();
+                    mInjectPresenter.attachView(this);
+                    field.setAccessible(true);
+                    field.set(this, mInjectPresenter);
+                    mInjectPresenters.add(mInjectPresenter);
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                } catch (java.lang.InstantiationException e) {
+                    e.printStackTrace();
+                }catch (ClassCastException e){
+                    e.printStackTrace();
+                    throw new RuntimeException("SubClass must extends Class:BasePresenter");
+                }
+            }
+        }
+    }
+
+    @Override
+    public void showToast(String message) {
+        ToastTool.showShort(message);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mInjectPresenters != null && mInjectPresenters.size() > 0) {
+            for (BasePresenter presenter : mInjectPresenters) {
+                presenter.detachView();
+            }
+            mInjectPresenters.clear();
+            mInjectPresenters = null;
+        }
+    }
+
+}

+ 12 - 0
mvp/src/main/java/com/miekir/mvp/view/IView.java

@@ -0,0 +1,12 @@
+package com.miekir.mvp.view;
+
+public interface IView {
+    void showLoading();
+
+    void showLoading(String msg);
+
+    void dismissLoading();
+
+    void showToast(String msg);
+
+}

+ 112 - 0
mvp/src/main/java/com/miekir/mvp/widget/LVCircularRing.java

@@ -0,0 +1,112 @@
+package com.miekir.mvp.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+
+public class LVCircularRing extends View {
+
+    private float mWidth = 0f;
+    private float mPadding = 0f;
+    private float startAngle = 0f;
+    private Paint mPaint;
+
+    public LVCircularRing(Context context) {
+        this(context, null);
+    }
+
+    public LVCircularRing(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LVCircularRing(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initPaint();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (getMeasuredWidth() > getHeight()){
+            mWidth = getMeasuredHeight();
+        }else{
+            mWidth = getMeasuredWidth();
+        }
+        mPadding = 5;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mPaint.setColor(Color.argb(100, 255, 255, 255));
+        canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2 - mPadding, mPaint);
+        mPaint.setColor(Color.WHITE);
+        RectF rectF = new RectF(mPadding, mPadding, mWidth - mPadding, mWidth - mPadding);
+        canvas.drawArc(rectF, startAngle, 100
+                , false, mPaint);//第四个参数是否显示半径
+
+    }
+
+    private void initPaint() {
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setColor(Color.WHITE);
+        mPaint.setStrokeWidth(8);
+    }
+
+    public void startAnim() {
+        stopAnim();
+        startViewAnim(0f, 1f, 1000);
+    }
+
+    public void stopAnim() {
+        if (valueAnimator != null) {
+            clearAnimation();
+            valueAnimator.setRepeatCount(1);
+            valueAnimator.cancel();
+            valueAnimator.end();
+        }
+    }
+
+    ValueAnimator valueAnimator;
+
+    private ValueAnimator startViewAnim(float startF, final float endF, long time) {
+        valueAnimator = ValueAnimator.ofFloat(startF, endF);
+        valueAnimator.setDuration(time);
+        valueAnimator.setInterpolator(new LinearInterpolator());
+        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);//无限循环
+        valueAnimator.setRepeatMode(ValueAnimator.RESTART);//
+
+        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                float value = (float) valueAnimator.getAnimatedValue();
+                startAngle = 360 * value;
+                invalidate();
+            }
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter(){
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+            }
+        });
+
+        if (!valueAnimator.isRunning()) {
+            valueAnimator.start();
+        }
+
+        return valueAnimator;
+    }
+}
+

+ 64 - 0
mvp/src/main/java/com/miekir/mvp/widget/LoadingDialog.java

@@ -0,0 +1,64 @@
+package com.miekir.mvp.widget;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.miekir.mvp.R;
+
+
+public class LoadingDialog {
+    LVCircularRing mLoadingView;
+    private TextView loadingText;
+    Dialog mLoadingDialog;
+    Context mContext;
+    private String msg = "加载中...";
+
+    public LoadingDialog(Context context) {
+        mContext = context;
+        // 首先得到整个View
+        View view = LayoutInflater.from(context).inflate(
+                R.layout.dialog_loading_view, null);
+        // 获取整个布局
+        LinearLayout dialogLayout = (LinearLayout) view.findViewById(R.id.dialog_view);
+        // 页面中的LoadingView
+        mLoadingView = (LVCircularRing) view.findViewById(R.id.lv_circularring);
+        // 页面中显示文本
+        loadingText = (TextView) view.findViewById(R.id.loading_text);
+        // 显示文本
+        loadingText.setText(msg);
+        // 创建自定义样式的Dialog
+        mLoadingDialog = new Dialog(context, R.style.loading_dialog);
+        // 设置返回键无效
+        mLoadingDialog.setCancelable(false);
+        mLoadingDialog.setContentView(dialogLayout, new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.MATCH_PARENT));
+    }
+
+
+    public void show() {
+        if (mLoadingDialog != null && !mLoadingDialog.isShowing()) {
+            mLoadingDialog.show();
+            mLoadingView.startAnim();
+        }
+    }
+
+    public void show(String message) {
+        if (mLoadingDialog != null && !mLoadingDialog.isShowing()) {
+            loadingText.setText(message);
+            mLoadingDialog.show();
+            mLoadingView.startAnim();
+        }
+    }
+
+    public void close() {
+        if (mLoadingDialog != null) {
+            mLoadingView.stopAnim();
+            mLoadingDialog.dismiss();
+        }
+    }
+}

+ 12 - 0
mvp/src/main/res/drawable/shape_dialog_bg.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- 内部颜色 -->
+    <solid android:color="#444444" />
+
+    <!-- 圆角的幅度 -->
+    <corners
+        android:bottomLeftRadius="3dp"
+        android:bottomRightRadius="3dp"
+        android:topLeftRadius="3dp"
+        android:topRightRadius="3dp" />
+</shape>

+ 26 - 0
mvp/src/main/res/layout/dialog_loading_view.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/dialog_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:background="@drawable/shape_dialog_bg"
+    android:padding="20dp"
+    android:orientation="vertical">
+
+    <com.miekir.mvp.widget.LVCircularRing
+        android:id="@+id/lv_circularring"
+        android:layout_width="50dp"
+        android:layout_height="50dp"/>
+
+    <TextView
+        android:id="@+id/loading_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="#ffffff"
+        android:layout_marginTop="5dp"
+        android:textSize="15sp"/>
+
+</LinearLayout>
+

+ 0 - 0
mvp/src/main/res/values/strings.xml


Vissa filer visades inte eftersom för många filer har ändrats