From 06e16847b074616b6578fe24171e96c2eecd9cf9 Mon Sep 17 00:00:00 2001 From: ScorpioMiku <1056992492@qq.com> Date: Sun, 2 Sep 2018 14:47:43 +0800 Subject: [PATCH] cardview --- .../nutritionmaster/adapter/CardAdapter.java | 10 + .../nutritionmaster/adapter/CardHolder.java | 10 + .../cardconfig/CardConfig.java | 25 ++ .../cardconfig/CardItemTouchCallBack.java | 254 ++++++++++++++++++ .../cardconfig/SwipeCardLayoutManager.java | 98 +++++++ .../CustomizationFragment.java} | 0 app/src/main/res/layout/card_item.xml | 6 + .../layout/{page_1.xml => customization.xml} | 0 8 files changed, 403 insertions(+) create mode 100644 app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardAdapter.java create mode 100644 app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardHolder.java create mode 100644 app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardConfig.java create mode 100644 app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardItemTouchCallBack.java create mode 100644 app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/SwipeCardLayoutManager.java rename app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/{page1/Page1.java => customization/CustomizationFragment.java} (100%) create mode 100644 app/src/main/res/layout/card_item.xml rename app/src/main/res/layout/{page_1.xml => customization.xml} (100%) diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardAdapter.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardAdapter.java new file mode 100644 index 0000000..d240ed5 --- /dev/null +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardAdapter.java @@ -0,0 +1,10 @@ +package com.example.ninefourone.nutritionmaster.adapter; + +import android.support.v7.widget.RecyclerView; + +/** + * Created by ScorpioMiku on 2018/9/2. + */ + +public class CardAdapter extends RecyclerView.Adapter { +} diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardHolder.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardHolder.java new file mode 100644 index 0000000..f3619ed --- /dev/null +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/adapter/CardHolder.java @@ -0,0 +1,10 @@ +package com.example.ninefourone.nutritionmaster.adapter; + +import android.support.v7.widget.RecyclerView; + +/** + * Created by ScorpioMiku on 2018/9/2. + */ + +public class CardHolder extends RecyclerView.ViewHolder { +} diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardConfig.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardConfig.java new file mode 100644 index 0000000..e50e45e --- /dev/null +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardConfig.java @@ -0,0 +1,25 @@ +package com.example.ninefourone.nutritionmaster.layoutmanager; + +import android.content.Context; +import android.util.TypedValue; + +/** + * 初始化一些配置信息、固定数据 + */ +public class CardConfig { + //屏幕上最多同时显示几个Item + public static int MAX_SHOW_COUNT; + + //每一级Scale相差0.05f,translationY相差15dp,translationZ相差0.5dp左右 + public static float SCALE_GAP; + public static int TRANS_Y_GAP; + public static int TRANS_Z_GAP; + + public static void initConfig(Context context) { + MAX_SHOW_COUNT = 4; + SCALE_GAP = 0.05f; + //这里是把dp转换成px + TRANS_Y_GAP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, context.getResources().getDisplayMetrics()); + TRANS_Z_GAP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.5f, context.getResources().getDisplayMetrics()); + } +} diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardItemTouchCallBack.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardItemTouchCallBack.java new file mode 100644 index 0000000..78fd43e --- /dev/null +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/CardItemTouchCallBack.java @@ -0,0 +1,254 @@ +package com.fu.tantancard; + +import android.graphics.Canvas; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.Log; +import android.view.View; + +import java.util.List; + +/** + * Created by Fu. + * QQ:908323236 + * 2017/11/10 11:27 + */ + +public class CardItemTouchCallBack extends ItemTouchHelper.Callback { + + private static final String TAG = "CardItemTouchCallBack"; + private RecyclerView mRecyclerView; + private MainActivity.CardAdapter mAdapter; + private List mDatas; + + public CardItemTouchCallBack(RecyclerView recyclerView, MainActivity.CardAdapter adapter, List datas) { + this.mRecyclerView = recyclerView; + this.mAdapter = adapter; + this.mDatas = datas; + } + + /** + * 是否开启长按拖拽 + * true,开启 + * false,不开启长按退拽 + * + * @return + */ + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + /** + * 是否开启滑动 + * true,开启 + * false,不开启长按退拽 + * + * @return + */ + @Override + public boolean isItemViewSwipeEnabled() { + return true; + } + + /** + * ItemTouchHelper支持设置事件方向,并且必须重写当前getMovementFlags来指定支持的方向 + * dragFlags 表示拖拽的方向,有六个类型的值:LEFT、RIGHT、START、END、UP、DOWN + * swipeFlags 表示滑动的方向,有六个类型的值:LEFT、RIGHT、START、END、UP、DOWN + * 最后要通过makeMovementFlags(dragFlag,swipe)创建方向的Flag + */ + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + /** + * 由于我们不需要长按拖拽,所以直接传入0即可,传入0代表不监听 + */ + int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN; + return makeMovementFlags(0, swipeFlags); + } + + /** + * 长按item就可以拖动,然后拖动到其他item的时候触发onMove + * 这里我们不需要 + * + * @param recyclerView + * @param viewHolder 拖动的viewholder + * @param target 目标位置的viewholder + * @return + */ + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + /** + * 把item滑走(飞出屏幕)的时候调用 + * + * @param viewHolder 滑动的viewholder + * @param direction 滑动的方向 + */ + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + //这里来判断画出的方向,左边是4,右边是8,然后可以做一些数据操作 + Log.d(TAG, "onSwiped: " + direction); + switch (direction) { + case 4: + Log.d(TAG, "onSwiped: 左边滑出"); + mAdapter.addDelCount(); + break; + case 8: + Log.d(TAG, "onSwiped: 右边滑出"); + mAdapter.addLoveCount(); + break; + } + //移除这条数据 + Object remove = mDatas.remove(viewHolder.getLayoutPosition()); + + /** 这个位置可以用来加载数据,当滑到还剩4个或者多少个时可以在后面加载数据,添加到mDatas中*/ + //这里就为了方便,直接循环了,把移除的元素再添加到末尾 + mDatas.add(mDatas.size(), remove); + + //刷新 + mAdapter.notifyDataSetChanged(); + //复位 + viewHolder.itemView.setRotation(0); + if (viewHolder instanceof MainActivity.CardAdapter.CardViewHolder) { + MainActivity.CardAdapter.CardViewHolder holder = (MainActivity.CardAdapter.CardViewHolder) viewHolder; + holder.iv_love.setAlpha(0f); + holder.iv_del.setAlpha(0f); + } + } + + /** + * 只要拖动、滑动了item,就会触发这个方法,而且是动的过程中会一直触发 + * 所以动画效果就是在这个方法中来实现的 + * + * @param c + * @param recyclerView + * @param viewHolder + * @param dX + * @param dY + * @param actionState + * @param isCurrentlyActive + */ + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, + float dX, float dY, int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + double swipeValue = Math.sqrt(dX * dX + dY * dY); //滑动离中心的距离 + double fraction = swipeValue / (mRecyclerView.getWidth() * 0.5f); + //边界修正 最大为1 + if (fraction > 1) { + fraction = 1; + } + + /** + * 调整每个子view的缩放、位移之类的 + */ + int childCount = recyclerView.getChildCount(); //拿到子view的数量 + isUpOrDown(mRecyclerView.getChildAt(childCount - 1)); + for (int i = 0; i < childCount; i++) { + /** 拿到子view 注意这里,先绘制的i=0,所以最下面一层view的i=0,最上面的i=3*/ + View childView = recyclerView.getChildAt(i); + int level = childCount - i - 1; //转换一下,level代表层数,最上面是第0层 + if (level > 0) { + //下面层,每一层的水平方向都要增大 + childView.setScaleX((float) (1 - CardConfig.SCALE_GAP * level + fraction * CardConfig.SCALE_GAP)); + if (level < CardConfig.MAX_SHOW_COUNT - 1) { + //1 2层 + childView.setScaleY((float) (1 - CardConfig.SCALE_GAP * level + fraction * CardConfig.SCALE_GAP)); + childView.setTranslationY((float) (CardConfig.TRANS_Y_GAP * level - fraction * CardConfig.TRANS_Y_GAP)); + childView.setTranslationZ((float) (CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1 - level) + + fraction * CardConfig.TRANS_Z_GAP)); + } else { + //最下面一层,3层,这层不用变,所以这里不用写 + } + } else { + //第0层 + //拿到水平方向的偏移比率 + float xFraction = dX / (mRecyclerView.getWidth() * 0.5f); + //边界修正,有正有负,因为旋转有两个方向 + if (xFraction > 1) { + xFraction = 1; + } else if (xFraction < -1) { + xFraction = -1; + } + //第一层左右滑动的时候稍微有点旋转 + childView.setRotation(xFraction * 15); //这里最多旋转15度 + + if (viewHolder instanceof MainActivity.CardAdapter.CardViewHolder) { + MainActivity.CardAdapter.CardViewHolder holder = (MainActivity.CardAdapter.CardViewHolder) viewHolder; + if (dX > 0) { + //右滑,显示爱心 + holder.iv_love.setAlpha(xFraction); + } else if (dX < 0) { + //左滑,显示叉,注意这里xFraction为负数,所以要取反 + holder.iv_del.setAlpha(-xFraction); + } else { + holder.iv_love.setAlpha(0f); + holder.iv_del.setAlpha(0f); + } + } + } + } + } + + @Override + public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { + Log.i(TAG, "getSwipeThreshold: "); + if (isUpOrDown(viewHolder.itemView)) { //如果是向上或者向下滑动 + return Float.MAX_VALUE; //就返回阈值为很大 + } + return super.getSwipeThreshold(viewHolder); + } + + /** + * 获得逃脱(swipe)速度 + * + * @param defaultValue + * @return + */ + @Override + public float getSwipeEscapeVelocity(float defaultValue) { + Log.d(TAG, "getSwipeEscapeVelocity: " + defaultValue); + View topView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1); + if (isUpOrDown(topView)) { //如果是向上或者向下滑动 + return Float.MAX_VALUE; //就返回阈值为很大 + } + return super.getSwipeEscapeVelocity(defaultValue); + } + + + /** + * 获得swipe的速度阈值 + * + * @param defaultValue + * @return + */ + @Override + public float getSwipeVelocityThreshold(float defaultValue) { + Log.d(TAG, "getSwipeVelocityThreshold: " + defaultValue); + View topView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1); + if (isUpOrDown(topView)) { //如果是向上或者向下滑动 + return Float.MAX_VALUE; //就返回阈值为很大 + } + return super.getSwipeVelocityThreshold(defaultValue); + } + + /** + * 判断是否是向上滑或者向下滑 + */ + private boolean isUpOrDown(View topView) { + float x = topView.getX(); + float y = topView.getY(); + int left = topView.getLeft(); + int top = topView.getTop(); + if (Math.pow(x - left, 2) > Math.pow(y - top, 2)) { + //水平方向大于垂直方向 +// Log.i(TAG, "isUpOrDown: 不是"); + return false; + } else { + return true; +// Log.i(TAG, "isUpOrDown: 是"); + } + } +} diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/SwipeCardLayoutManager.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/SwipeCardLayoutManager.java new file mode 100644 index 0000000..c781faf --- /dev/null +++ b/app/src/main/java/com/example/ninefourone/nutritionmaster/cardconfig/SwipeCardLayoutManager.java @@ -0,0 +1,98 @@ +package com.example.ninefourone.nutritionmaster.layoutmanager; + +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by ScorpioMiku on 2018/9/2. + */ + +public class SwipeCardLayoutManager extends RecyclerView.LayoutManager { + @Override + public RecyclerView.LayoutParams generateDefaultLayoutParams() { + return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + /** + * 在这里面给子view布局,也就是item + * + * @param recycler + * @param state + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + //在布局之前,将所有的子View先Detach掉,放入到Scrap缓存中 + detachAndScrapAttachedViews(recycler); + + //拿到总item数量 + int itemCount = getItemCount(); + if (itemCount < 1) {//没有item当然就没必要布局了 + return; + } + int bottomPosition; //用来记录最底层view的postion + + if (itemCount < CardConfig.MAX_SHOW_COUNT) { //如果不足最大数量(4个) + //那么最底层就是最后一条数据对应的position + bottomPosition = itemCount - 1; + } else { + //否则最底层就是第MAX_SHOW_COUNT(4)条数据对应的position + bottomPosition = CardConfig.MAX_SHOW_COUNT - 1; + } + + /** + * 这里开始布局且绘制子view + * 注意:这里要先从最底层开始绘制,因为后绘制的才能覆盖先绘制的, + * 滑动的时候是滑最上面一层的,也就是后绘制的 + * position也是层数 + */ + for (int position = bottomPosition; position >= 0; position--) { + //根据position找recycler要itemview + View view = recycler.getViewForPosition(position); + //将子View添加至RecyclerView中 + addView(view); + //测量子view并且把Margin也作为子控件的一部分 + measureChildWithMargins(view, 0, 0); + //宽度空隙 getWidth()得到Recycler控件的宽度,getDecoratedMeasuredWidth(view)拿到子view的宽度 + int widthSpace = getWidth() - getDecoratedMeasuredWidth(view); + //高度空隙 + int heightSpace = getHeight() - getDecoratedMeasuredHeight(view); + //给子view布局,这里居中了 + layoutDecoratedWithMargins(view, widthSpace / 2, 200, + widthSpace / 2 + getDecoratedMeasuredWidth(view), + getDecoratedMeasuredHeight(view) + 200); + + /** + * 下面要调整每一层itemview的的大小及Y轴和Z轴的偏移 + * 最上面一层(第0层)的Scale为1,translationY为0 + * 依次往下,每层比上面一层: + * (1)Scale相差0.05f + * (2)translationY相差7dp + * (3)translationZ相差1dp + * + * 注意:最后一层,除了水平方向的大小其他都与上一层一样,所以要特殊判断 + */ + if (position > 0) { //大于0就是不是最上面那层 + //依次往下,每层都要水平方向缩小 + view.setScaleX(1 - CardConfig.SCALE_GAP * position); + if (position < CardConfig.MAX_SHOW_COUNT - 1) { + //如果,不是最后一层,就都要调整 + view.setScaleY(1 - CardConfig.SCALE_GAP * position); //垂直方向缩小 + view.setTranslationY(CardConfig.TRANS_Y_GAP * position); //向下平移 + view.setTranslationZ(CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1 - position)); //Z轴方向的平移 + } else { + //否则,就是最后一层,与上一层保持一致 + view.setScaleY(1 - CardConfig.SCALE_GAP * (position - 1)); //垂直方向缩小 + view.setTranslationY(CardConfig.TRANS_Y_GAP * (position - 1)); //向下平移 + view.setTranslationZ(CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1 - (position - 1))); //Z轴方向的平移 + } + } else { + //否则,是第0层(最上面那层),只需调整Z轴高度 + view.setTranslationZ(CardConfig.TRANS_Z_GAP * (CardConfig.MAX_SHOW_COUNT - 1)); //Z轴方向的平移 + } + } + } +} diff --git a/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/page1/Page1.java b/app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/customization/CustomizationFragment.java similarity index 100% rename from app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/page1/Page1.java rename to app/src/main/java/com/example/ninefourone/nutritionmaster/modules/viewpagerfragments/customization/CustomizationFragment.java diff --git a/app/src/main/res/layout/card_item.xml b/app/src/main/res/layout/card_item.xml new file mode 100644 index 0000000..d8fb324 --- /dev/null +++ b/app/src/main/res/layout/card_item.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/page_1.xml b/app/src/main/res/layout/customization.xml similarity index 100% rename from app/src/main/res/layout/page_1.xml rename to app/src/main/res/layout/customization.xml