+ * Created by DavidChen on 2018/5/29.
+ */
+
+public class SlideRecyclerView extends RecyclerView {
+ private static final String TAG = "SlideRecyclerView";
+ private static final int INVALID_POSITION = -1; // 触摸到的点不在子View范围内
+ private static final int INVALID_CHILD_WIDTH = -1; // 子ItemView不含两个子View
+ private static final int SNAP_VELOCITY = 600; // 最小滑动速度
+
+ private VelocityTracker mVelocityTracker; // 速度追踪器
+ private int mTouchSlop; // 认为是滑动的最小距离(一般由系统提供)
+ private Rect mTouchFrame; // 子View所在的矩形范围
+ private Scroller mScroller;
+ private float mLastX; // 滑动过程中记录上次触碰点X
+ private float mFirstX, mFirstY; // 首次触碰范围
+ private boolean mIsSlide; // 是否滑动子View
+ private ViewGroup mFlingView; // 触碰的子View
+ private int mPosition; // 触碰的view的位置
+ private int mMenuViewWidth; // 菜单按钮宽度
+
+ public SlideRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public SlideRecyclerView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlideRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mScroller = new Scroller(context);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ int x = (int) e.getX();
+ int y = (int) e.getY();
+ obtainVelocity(e);
+ switch (e.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (!mScroller.isFinished()) { // 如果动画还没停止,则立即终止动画
+ mScroller.abortAnimation();
+ }
+ mFirstX = mLastX = x;
+ mFirstY = y;
+ mPosition = pointToPosition(x, y); // 获取触碰点所在的position
+ if (mPosition != INVALID_POSITION) {
+ View view = mFlingView;
+ // 获取触碰点所在的view
+ mFlingView = (ViewGroup) getChildAt(mPosition - ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition());
+ // 这里判断一下如果之前触碰的view已经打开,而当前碰到的view不是那个view则立即关闭之前的view,此处并不需要担动画没完成冲突,因为之前已经abortAnimation
+ if (view != null && mFlingView != view && view.getScrollX() != 0) {
+ view.scrollTo(0, 0);
+ }
+ // 这里进行了强制的要求,RecyclerView的子ViewGroup必须要有2个子view,这样菜单按钮才会有值,
+ // 需要注意的是:如果不定制RecyclerView的子View,则要求子View必须要有固定的width。
+ // 比如使用LinearLayout作为根布局,而content部分width已经是match_parent,此时如果菜单view用的是wrap_content,menu的宽度就会为0。
+ if (mFlingView.getChildCount() == 2) {
+ mMenuViewWidth = mFlingView.getChildAt(1).getWidth();
+ } else {
+ mMenuViewWidth = INVALID_CHILD_WIDTH;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mVelocityTracker.computeCurrentVelocity(1000);
+ // 此处有俩判断,满足其一则认为是侧滑:
+ // 1.如果x方向速度大于y方向速度,且大于最小速度限制;
+ // 2.如果x方向的侧滑距离大于y方向滑动距离,且x方向达到最小滑动距离;
+ float xVelocity = mVelocityTracker.getXVelocity();
+ float yVelocity = mVelocityTracker.getYVelocity();
+ if (Math.abs(xVelocity) > SNAP_VELOCITY && Math.abs(xVelocity) > Math.abs(yVelocity)
+ || Math.abs(x - mFirstX) >= mTouchSlop
+ && Math.abs(x - mFirstX) > Math.abs(y - mFirstY)) {
+ mIsSlide = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ releaseVelocity();
+ break;
+ }
+ return super.onInterceptTouchEvent(e);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (mIsSlide && mPosition != INVALID_POSITION) {
+ float x = e.getX();
+ obtainVelocity(e);
+ switch (e.getAction()) {
+ case MotionEvent.ACTION_DOWN: // 因为没有拦截,所以不会被调用到
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // 随手指滑动
+ if (mMenuViewWidth != INVALID_CHILD_WIDTH) {
+ float dx = mLastX - x;
+ if (mFlingView.getScrollX() + dx <= mMenuViewWidth
+ && mFlingView.getScrollX() + dx > 0) {
+ mFlingView.scrollBy((int) dx, 0);
+ }
+ mLastX = x;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mMenuViewWidth != INVALID_CHILD_WIDTH) {
+ int scrollX = mFlingView.getScrollX();
+ mVelocityTracker.computeCurrentVelocity(1000);
+ // 此处有两个原因决定是否打开菜单:
+ // 1.菜单被拉出宽度大于菜单宽度一半;
+ // 2.横向滑动速度大于最小滑动速度;
+ // 注意:之所以要小于负值,是因为向左滑则速度为负值
+ if (mVelocityTracker.getXVelocity() < -SNAP_VELOCITY) { // 向左侧滑达到侧滑最低速度,则打开
+ mScroller.startScroll(scrollX, 0, mMenuViewWidth - scrollX, 0, Math.abs(mMenuViewWidth - scrollX));
+ } else if (mVelocityTracker.getXVelocity() >= SNAP_VELOCITY) { // 向右侧滑达到侧滑最低速度,则关闭
+ mScroller.startScroll(scrollX, 0, -scrollX, 0, Math.abs(scrollX));
+ } else if (scrollX >= mMenuViewWidth / 2) { // 如果超过删除按钮一半,则打开
+ mScroller.startScroll(scrollX, 0, mMenuViewWidth - scrollX, 0, Math.abs(mMenuViewWidth - scrollX));
+ } else { // 其他情况则关闭
+ mScroller.startScroll(scrollX, 0, -scrollX, 0, Math.abs(scrollX));
+ }
+ invalidate();
+ }
+ mMenuViewWidth = INVALID_CHILD_WIDTH;
+ mIsSlide = false;
+ mPosition = INVALID_POSITION;
+ releaseVelocity(); // 这里之所以会调用,是因为如果前面拦截了,就不会执行ACTION_UP,需要在这里释放追踪
+ break;
+ }
+ return true;
+ } else {
+ // 此处防止RecyclerView正常滑动时,还有菜单未关闭
+ closeMenu();
+ // Velocity,这里的释放是防止RecyclerView正常拦截了,但是在onTouchEvent中却没有被释放;
+ // 有三种情况:1.onInterceptTouchEvent并未拦截,在onInterceptTouchEvent方法中,DOWN和UP一对获取和释放;
+ // 2.onInterceptTouchEvent拦截,DOWN获取,但事件不是被侧滑处理,需要在这里进行释放;
+ // 3.onInterceptTouchEvent拦截,DOWN获取,事件被侧滑处理,则在onTouchEvent的UP中释放。
+ releaseVelocity();
+ }
+ return super.onTouchEvent(e);
+ }
+
+ private void releaseVelocity() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void obtainVelocity(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ }
+
+ public int pointToPosition(int x, int y) {
+ if (null == getLayoutManager()) return INVALID_POSITION;
+ int firstPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
+ Rect frame = mTouchFrame;
+ if (frame == null) {
+ mTouchFrame = new Rect();
+ frame = mTouchFrame;
+ }
+
+ final int count = getChildCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE) {
+ child.getHitRect(frame);
+ if (frame.contains(x, y)) {
+ return firstPosition + i;
+ }
+ }
+ }
+ return INVALID_POSITION;
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ mFlingView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ invalidate();
+ }
+ }
+
+ /**
+ * 将显示子菜单的子view关闭
+ * 这里本身是要自己来实现的,但是由于不定制item,因此不好监听器点击事件,因此需要调用者手动的关闭
+ */
+ public void closeMenu() {
+ if (mFlingView != null && mFlingView.getScrollX() != 0) {
+ mFlingView.scrollTo(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/commonLib/src/main/java/com/yinuo/commonlib/view/SpaceItemDecoration.java b/commonLib/src/main/java/com/yinuo/commonlib/view/SpaceItemDecoration.java
new file mode 100644
index 0000000..7e92367
--- /dev/null
+++ b/commonLib/src/main/java/com/yinuo/commonlib/view/SpaceItemDecoration.java
@@ -0,0 +1,32 @@
+package com.yinuo.commonlib.view;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.jetbrains.annotations.NotNull;
+
+public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
+ /**
+ * 默认图片展示单行数量
+ */
+ private static final int DEFAULT_MEDIA_SIZE = 3;
+
+ /**
+ * 默认图片展示间距
+ */
+ private static final int DEFAULT_MEDIA_OFFSET = 30;
+
+ @Override
+ public void getItemOffsets(@NotNull Rect outRect, @NotNull View view, RecyclerView parent, @NotNull RecyclerView.State state) {
+ int temp = parent.getChildAdapterPosition(view) % DEFAULT_MEDIA_SIZE;
+ outRect.left = DEFAULT_MEDIA_OFFSET * (temp);
+
+ if (parent.getChildAdapterPosition(view) >= DEFAULT_MEDIA_SIZE) {
+ outRect.top = DEFAULT_MEDIA_OFFSET;
+ } else {
+ outRect.top = 0;
+ }
+ }
+}
diff --git a/commonLib/src/main/java/com/yinuo/commonlib/view/activity/BaseActivity.java b/commonLib/src/main/java/com/yinuo/commonlib/view/activity/BaseActivity.java
new file mode 100644
index 0000000..d25a06c
--- /dev/null
+++ b/commonLib/src/main/java/com/yinuo/commonlib/view/activity/BaseActivity.java
@@ -0,0 +1,28 @@
+package com.yinuo.commonlib.view.activity;
+
+import android.os.Bundle;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.yinuo.commonlib.R;
+
+public class BaseActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initStatusBar();
+ }
+
+ private void initStatusBar() {
+ Window window = getWindow();
+ //After LOLLIPOP not translucent status bar
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ //Then call setStatusBarColor.
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.setStatusBarColor(getResources().getColor(R.color.title_bar_bg_color));
+ }
+}
diff --git a/commonLib/src/main/java/com/yinuo/commonlib/view/fragment/BaseFragment.java b/commonLib/src/main/java/com/yinuo/commonlib/view/fragment/BaseFragment.java
new file mode 100644
index 0000000..2e2c6ca
--- /dev/null
+++ b/commonLib/src/main/java/com/yinuo/commonlib/view/fragment/BaseFragment.java
@@ -0,0 +1,18 @@
+package com.yinuo.commonlib.view.fragment;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import org.jetbrains.annotations.NotNull;
+
+public class BaseFragment extends Fragment {
+ public Context mContext;
+
+ @Override
+ public void onAttach(@NonNull @NotNull Context context) {
+ super.onAttach(context);
+ mContext = context;
+ }
+}
diff --git a/commonLib/src/main/java/com/yinuo/commonlib/view/fragment/BaseNavigationFragment.kt b/commonLib/src/main/java/com/yinuo/commonlib/view/fragment/BaseNavigationFragment.kt
new file mode 100644
index 0000000..f0f7b44
--- /dev/null
+++ b/commonLib/src/main/java/com/yinuo/commonlib/view/fragment/BaseNavigationFragment.kt
@@ -0,0 +1,59 @@
+package com.yinuo.commonlib.view.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+abstract class BaseNavigationFragment : BaseFragment() {
+ private var isNavigationViewInit = false//记录是否已经初始化过一次视图
+ private var lastView: View? = null//记录上次创建的view
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ doPrepare()
+ //如果fragment的view已经创建则不再重新创建
+ if (lastView == null) {
+ lastView = getRootView(
+ inflater,
+ container,
+ savedInstanceState
+ )
+ }
+ return lastView
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ if (!isNavigationViewInit) {//初始化过视图则不再进行view和data初始化
+ super.onViewCreated(view, savedInstanceState)
+ initView()
+ initData()
+ initOthers()
+ isNavigationViewInit = true
+ }
+ }
+
+ abstract fun getRootView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View?
+
+ abstract fun initData()
+
+ abstract fun initView()
+
+ abstract fun initOthers()
+
+ abstract fun doPrepare()
+
+ fun getIsNavigationViewInit():Boolean{
+ return isNavigationViewInit
+ }
+
+ fun setIsNavigationViewInit(isNavigationViewInit:Boolean){
+ this.isNavigationViewInit=isNavigationViewInit
+ }
+}
\ No newline at end of file
diff --git a/commonLib/src/main/res/drawable-anydpi/ic_check_box_checked.xml b/commonLib/src/main/res/drawable-anydpi/ic_check_box_checked.xml
new file mode 100644
index 0000000..f2e1d31
--- /dev/null
+++ b/commonLib/src/main/res/drawable-anydpi/ic_check_box_checked.xml
@@ -0,0 +1,11 @@
+