-
2019-08-26 21:38:53
layui的多图上传功能相当的方便。
html:部分
<div class="layui-form"> <div class="layui-form-item"> <label class="layui-form-label">活动图片:</label> <div class="layui-input-block"> <div class="layui-upload"> <button type="button" class="layui-btn" id="uploadImgsBtn"> 多图片上传 </button> <div class="layui-upload-list" id="showUploadImgs"></div> </div> </div> </div> </div>
文章已移至
更多相关内容 -
仿微信朋友圈发表图片拖拽和删除功能
2022-01-19 10:13:06仿微信朋友圈发表图片拖拽和删除功能看这图片我们想到的是使用 RecyclerView 自带的ItemTouchHelper对拖拽进行处理,那先来一些知识的储备
一:RecyclerView 的ItemTouchHelper
官方解释:
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView 一个让 RecyclerView 支持滑动删除和拖拽的实用工具类
主要方法
//关联对应的 RecyclerView public void attachToRecyclerView(RecyclerView recyclerView) //viewHolder开始拖动 public void startDrag(RecyclerView.ViewHolder viewHolder) //viewHolder开始滑动 public void startSwipe(RecyclerView.ViewHolder viewHolder)
使用
- 自定义一个类继承并实现ItemTouchHelper.Callback接口,以下方法必须实现:
//设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向 @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { } //当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用 @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { } //滑动到消失后的调用 @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { }
- 实现化ItemTouchHelper并关联RecyclerView
itemTouchHelper = new ItemTouchHelper(myCallBack); itemTouchHelper.attachToRecyclerView(recyclerView);
二:ItemTouchHelper.Callback
ItemTouchHelper在拖拽和滑动删除的过程中会回调ItemTouchHelper.Callback的相关方法
主要方法
//设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向 public int getMovementFlags (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) /** *当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用 *@recyclerView *@viewHolder 拖动的 item *@target 目标 item **/ public boolean onMove (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) /** * RecyclerView调用onDraw时调用,如果想自定义item对用户互动的响应,可以重写该方法 * @dx item 滑动的距离 **/ public void onChildDraw (Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) //设置是否可以长按拖拽 public boolean isLongPressDragEnabled () //设置手指离开后ViewHolder的动画时间,在用户手指离开后调用 public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) //当长按选中item的时候(拖拽开始的时候)调用 public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) //当用户与item的交互结束并且item也完成了动画时调用 public void clearView (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
三:实现过程
1:从布局入手
对应的xml布局如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:focusableInTouchMode="true" android:orientation="vertical" tools:context=".module.feedback.add.FeedBackAddActivity"> <androidx.core.widget.NestedScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false"> <com.snxun.zzypt.module.feedback.layout.EditTextWithScrollView android:id="@+id/feedback_content_edit" android:layout_width="match_parent" android:layout_height="130dp" android:autofillHints="@string/feedback_msg" android:background="@null" android:gravity="start" android:hint="@string/feedback_msg" android:inputType="textMultiLine" android:maxLength="200" android:paddingStart="20dp" android:paddingTop="14dp" android:paddingEnd="20dp" android:paddingBottom="14dp" android:textColor="@color/color_d9000000" android:textColorHint="@color/color_40000000" android:textSize="16sp" /> <LinearLayout android:id="@+id/bottom_ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:layout_marginTop="280dp" android:layout_marginEnd="20dp" android:orientation="vertical" android:paddingBottom="15dp"> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/color_17000000" /> <TextView android:id="@+id/feedback_history_tv" android:layout_width="match_parent" android:layout_height="50dp" android:drawablePadding="5dp" android:gravity="center_vertical" android:text="@string/feedback_history" android:textColor="@color/color_d9000000" android:textSize="14sp" app:drawableEndCompat="@drawable/ic_right" app:drawableStartCompat="@drawable/ic_history" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/color_17000000" /> </LinearLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/pic_rv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/feedback_content_edit" android:layout_marginStart="20dp" android:layout_marginEnd="20dp" android:nestedScrollingEnabled="false" app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager" app:spanCount="3" /> </RelativeLayout> </androidx.core.widget.NestedScrollView> <LinearLayout android:id="@+id/delete_area_view" android:layout_width="match_parent" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:background="@color/color_FFF5222D" android:gravity="center" android:visibility="invisible"> <TextView android:id="@+id/delete_area_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawablePadding="5dp" android:gravity="center" android:text="@string/delete_pic" android:textColor="@color/white" app:drawableStartCompat="@drawable/ic_delete_white" /> </LinearLayout> </RelativeLayout>
EditTextWithScrollView这个类:
我们设置输入框的高度是固定的,当输入的内容高于设置的高度时可进行滚动查看,用于解决嵌套Scrollview的时候由于多行而产生的滑动冲突问题,
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import androidx.appcompat.widget.AppCompatEditText; /** * 用于解决嵌套Scrollview的时候由于多行而产生的滑动冲突问题 * * @author Wuczh * @date 2021/11/30 */ public class EditTextWithScrollView extends AppCompatEditText { //滑动距离的最大边界 private int mOffsetHeight; //是否到顶或者到底的标志 private boolean mBottomFlag = false; private boolean mCanVerticalScroll; public EditTextWithScrollView(Context context) { super(context); init(); } public EditTextWithScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public EditTextWithScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mCanVerticalScroll = canVerticalScroll(); } @Override public boolean dispatchTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) //如果是新的按下事件,则对mBottomFlag重新初始化 mBottomFlag = false; //如果已经不要这次事件,则传出取消的信号,这里的作用不大 if (mBottomFlag) event.setAction(MotionEvent.ACTION_CANCEL); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { boolean result = super.onTouchEvent(event); if (mCanVerticalScroll) { //如果是需要拦截,则再拦截,这个方法会在onScrollChanged方法之后再调用一次 if (!mBottomFlag) getParent().requestDisallowInterceptTouchEvent(true); } else { getParent().requestDisallowInterceptTouchEvent(false); } return result; } @Override protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { super.onScrollChanged(horiz, vert, oldHoriz, oldVert); if (vert == mOffsetHeight || vert == 0) { //这里触发父布局或祖父布局的滑动事件 getParent().requestDisallowInterceptTouchEvent(false); mBottomFlag = true; } } /** * EditText竖直方向是否可以滚动 * * @return true:可以滚动 false:不可以滚动 */ private boolean canVerticalScroll() { //滚动的距离 int scrollY = getScrollY(); //控件内容的总高度 int scrollRange = getLayout().getHeight(); //控件实际显示的高度 int scrollExtent = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); //控件内容总高度与实际显示高度的差值 mOffsetHeight = scrollRange - scrollExtent; if (mOffsetHeight == 0) { return false; } return (scrollY > 0) || (scrollY < mOffsetHeight - 1); } }
三个属性的使用
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"Android开发实战(二十一):浅谈android:clipChildren属性 - 云+社区 - 腾讯云
android:clipToPadding的使用_wangjiang-CSDN博客_android cliptopadding
ScrollView中添加一个android:fillViewport="true"_Jsoh的博客-CSDN博客_android fillviewport
设置RecyclerView的高度为android:layout_height="wrap_content",要想item可以在整个布局拖动,我们设置父布局RelativeLayout为
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false">
这样设置能实现全局拖动的效果吗?我们来看下下面的效果,发现item只可在父布局的范围内拖动,这时候需要我们将NestedScrollView加上属性fillViewport="true",这样RelativeLayout就可填充NestedScrollView,也就是全屏
<androidx.core.widget.NestedScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true">
下方显示的布局的设置:
以一般的判断我们会将底下布局,直接在代码里设置相对与RecyclerView底部显示,那看下如果这样设置会出现什么情况:
从图中可以看到,在移动item的时候,下面布局的显示位置会发生错乱,所以我们采用的是具体的显示位置在代码中进行位置计算后设置的。具体的计算我们在后面进行说明。
2:设置ItemTouchHelperCallback继承ItemTouchHelper.Callback
具体的实现在代码中都有很详细的备注
import android.graphics.Canvas; import androidx.annotation.NonNull; import androidx.core.widget.NestedScrollView; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; import com.lodz.android.corekt.album.PicInfo; import com.lodz.android.corekt.anko.AnkoAnimKt; import com.lodz.android.corekt.anko.AnkoArrayKt; import com.lodz.android.corekt.anko.AnkoDimensionsKt; import com.lodz.android.corekt.anko.AnkoVibratorKt; import com.snxun.zzypt.App; import com.snxun.zzypt.module.feedback.add.adapter.PhotoPublishAdapter; import java.util.ArrayList; import java.util.Collections; /** * 拖拽排序删除 * * @author Wuczh * @date 2021/11/18 */ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback { /** * 是否需要拖拽震动提醒 */ private boolean isNeedDragVibrate = true; /** * 适配器 */ private PhotoPublishAdapter mAdapter; /** * 手指抬起标记位 */ private boolean up; /** * 可滑动伸缩空间 */ private NestedScrollView mScrollView; /** * 数据列表 */ private ArrayList<PicInfo> mDataList; private int dragFlags; private int swipeFlags; /** * @param isNeedDragVibrate 是否需要拖拽震动提醒 * @param imagesList 图片数据 * @param scrollView 可滑动伸缩空间 * @param adapter 适配器 */ public ItemTouchHelperCallback(boolean isNeedDragVibrate, ArrayList<PicInfo> imagesList, NestedScrollView scrollView, PhotoPublishAdapter adapter) { this.isNeedDragVibrate = isNeedDragVibrate; mAdapter = adapter; mScrollView = scrollView; mDataList = imagesList; } /** * 设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向 */ @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { //判断 recyclerView的布局管理器数据,设置 item 只能处理拖拽事件,并能够向左、右、上、下拖拽 if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {//设置能拖拽的方向 dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; swipeFlags = 0;//0则不响应滑动事件 } return makeMovementFlags(dragFlags, swipeFlags); } /** * 拖拽,交换位置(当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用) * * @param viewHolder 拖动的 item * @param target 目标 item */ @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { if (AnkoArrayKt.isNullOrEmpty(mDataList)) { return false; } // 得到拖动ViewHolder的Position int fromPosition = viewHolder.getBindingAdapterPosition(); // 得到目标ViewHolder的Position int toPosition = target.getBindingAdapterPosition(); //因为没有将 +号的图片 加入imageList,所以不用imageList.size-1 此处限制不能移动到recyclerView最后一位 if (toPosition == mDataList.size() || mDataList.size() == fromPosition) { return false; } if (fromPosition < toPosition) {//顺序小到大 for (int i = fromPosition; i < toPosition; i++) { Collections.swap(mDataList, i, i + 1); } } else {//顺序大到小 for (int i = fromPosition; i > toPosition; i--) { Collections.swap(mDataList, i, i - 1); } } mAdapter.notifyItemMoved(fromPosition, toPosition); return true; } /** * 滑动 */ @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { } /** * 当长按选中item时(拖拽开始时)调用 * ItemTouchHelper.ACTION_STATE_IDLE 闲置状态 * ItemTouchHelper.ACTION_STATE_DRAG 拖拽中状态 */ @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { if (viewHolder == null) { return; } //设置拖拽震动提醒 if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && isNeedDragVibrate) { AnkoVibratorKt.createVibrator(App.getInstance(), 100); } //设置拖拽动画 if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {//开始拖拽 AnkoAnimKt.startScaleSelf(viewHolder.itemView, 1.0f, 1.05f, 1.0f, 1.05f, 50, true); } //设置拖拽状态为true if (ItemTouchHelper.ACTION_STATE_DRAG == actionState && dragListener != null) { dragListener.dragState(true); } super.onSelectedChanged(viewHolder, actionState); } /** * 当手指松开时(拖拽完成时)调用 * 在clearView()方法里去notifyDataSetChanged,不然 item的position是没有交换的 */ @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { AnkoAnimKt.startScaleSelf(viewHolder.itemView, 1.05f, 1.0f, 1.05f, 1.0f, 50, true); super.clearView(recyclerView, viewHolder); initData(); if (dragListener != null) { dragListener.clearView(); } } /** * 自定义拖动与滑动交互 * * @param c * @param recyclerView * @param viewHolder * @param dX X轴移动的距离 * @param dY Y轴移动的距离 * @param actionState 当前Item的状态 * @param isCurrentlyActive 如果当前被用户操作为true,反之为false */ @Override public void onChildDraw(Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (null == dragListener) { return; } int editTextHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 130);//输入框的高度 int deleteViewHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 50);//删除区间的高度 /** *滑动的距离到达删除区域时的判断 */ if (dY >= (mScrollView.getHeight() - editTextHeight) - viewHolder.itemView.getBottom()//item底部距离recyclerView顶部高度 - deleteViewHeight + mScrollView.getScrollY()) {//拖到删除处 dragListener.deleteState(true); if (up) {//在删除处放手,则删除item mDataList.remove(viewHolder.getBindingAdapterPosition()); dragListener.deleteOk(); mAdapter.notifyDataSetChanged(); initData(); return; } } else {//没有到删除处 dragListener.deleteState(false); } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } /** * 设置是否支持长按拖拽 * 此处必须返回false * 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽 */ @Override public boolean isLongPressDragEnabled() { return false; } /** * 设置是否支持支持滑动 * * @return true 支持滑动操作 * false 不支持滑动操作 */ @Override public boolean isItemViewSwipeEnabled() { return false; } /** * 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用 * * @param recyclerView * @param animationType * @param animateDx * @param animateDy * @return */ @Override public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, float animateDx, float animateDy) { //手指放开 up = true; return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy); } public interface DragListener { /** * 用户是否将 item拖动到删除处,根据状态改变颜色 * * @param delete */ void deleteState(boolean delete); /** * 是否于拖拽状态 * * @param start */ void dragState(boolean start); /** * 当删除完成后调用 */ void deleteOk(); /** * 当用户与item的交互结束并且item也完成了动画时调用 */ void clearView(); } private DragListener dragListener; public void setDragListener(DragListener dragListener) { this.dragListener = dragListener; } /** * 重置状态(拖拽状态设置为false 删除状态为false) */ private void initData() { if (dragListener != null) { dragListener.deleteState(false); dragListener.dragState(false); } up = false; } }
- 重点是实现拖拽到底部放手删除功能
item从拖动到放手的主要处理流程图如下;
那么我们应该在哪个地方去判断item是否到达删除区域呢?在前面介绍的方法,有这么个方法:onChildDraw,这个方法就会在item拖拽的过程不断回调并且返回item的偏移量。有了偏移量之后我们就很容易去判断item是否到达删除区域了。
偏移量满足以下条件时,就到达删除区域:
item偏移量>=RecyclerView的高-item底部到RecyclerView顶边的距离-.EditText的高+ScrollView.getScrollY()的滑动距离从上面的图片看起来,似乎没有体现ScrollView.getScrollY()的高度。可以观察下视频里的滑动效果,视频里是没有加上ScrollView.getScrollY()的高度的判断,发现在整个屏幕没有往上滑动的时候,item进行删除都是会到达删除区域的时候,但当我们把界面手动上划的时候发现item还没到达删除区域就会进入删除状态,这其实是dY的这个距离是包含ScrollView.getScrollY()的高度。
视频效果:滑动效果-CSDN直播
- 怎样判断用户在拖动后放手呢?
用boolean up来标记,当up为true时手指抬起,false为初始状态。在getAnimationDuration()中设置其为true。记得需要在clearView()中恢复初始值false;
还需要在ItemTouchHelper.Callback中暴露个接口DragListener给外部,用来提示通知外部什么时候显示删除区域,以及item进入删除区域时的文字提示。/** * 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用 * * @param recyclerView * @param animationType * @param animateDx * @param animateDy * @return */ @Override public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, float animateDx, float animateDy) { //手指放开 up = true; return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy); } /** * 当手指松开时(拖拽完成时)调用,重置状态 */ @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { AnkoAnimKt.startScaleSelf(viewHolder.itemView, 1.05f, 1.0f, 1.05f, 1.0f, 50, true); super.clearView(recyclerView, viewHolder); initData(); if (dragListener != null) { dragListener.clearView(); } } /** * 重置状态(拖拽状态设置为false 删除状态为false) */ private void initData() { if (dragListener != null) { dragListener.deleteState(false); dragListener.dragState(false); } up = false; } /** * 自定义拖动与滑动交互 * * @param c * @param recyclerView * @param viewHolder * @param dX X轴移动的距离 * @param dY Y轴移动的距离 * @param actionState 当前Item的状态 * @param isCurrentlyActive 如果当前被用户操作为true,反之为false */ @Override public void onChildDraw(Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (null == dragListener) { return; } int editTextHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 130);//输入框的高度 int deleteViewHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 50);//删除区间的高度 /** * scrollView.getHeight()-editTextHeight 为recyclerview的高度 * 此处不用onChildDraw里的参数recyclerView.getHeight来计算,因为当添加图片至超出屏幕高度 * 即scrollView可以滑动后获取的recyclerview不准确,亲测。 */ if (dY >= (mScrollView.getHeight() - editTextHeight) - viewHolder.itemView.getBottom()//item底部距离recyclerView顶部高度 - deleteViewHeight + mScrollView.getScrollY()) {//拖到删除处 dragListener.deleteState(true); if (up) {//在删除处放手,则删除item mDataList.remove(viewHolder.getBindingAdapterPosition()); dragListener.deleteOk(); mAdapter.notifyDataSetChanged(); initData(); return; } } else {//没有到删除处 dragListener.deleteState(false); } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); }
3. 关联 RecyclerView
ItemTouchHelperCallback myCallBack = new ItemTouchHelperCallback(false, mDataList, mBinding.scrollView, mAdapter); mItemTouchHelper = new ItemTouchHelper(myCallBack);//实现化ItemTouchHelper(拖拽和滑动删除的过程中会回调ItemTouchHelper.Callback的相关方法) mItemTouchHelper.attachToRecyclerView(mBinding.picRv);//关联对应的 RecyclerView
//滑动状态回调处理 myCallBack.setDragListener(new ItemTouchHelperCallback.DragListener() { @Override public void deleteState(boolean delete) { //根据是否是删除状态,显示对应的删除区域的文字和背景色 if (delete) { mBinding.deleteAreaView.setAlpha(0.9f); mBinding.deleteAreaTv.setText(getString(R.string.loosen_delete_pic)); } else { mBinding.deleteAreaView.setAlpha(0.6f); mBinding.deleteAreaTv.setText(R.string.drag_delete_pic); } } @Override public void dragState(boolean start) { //根据是否是开始滑动状态,来设置是否显示删除区域 if (start) { mBinding.deleteAreaView.setVisibility(View.VISIBLE); } else { mBinding.deleteAreaView.setVisibility(View.GONE); } } @Override public void deleteOk() { //删除后重新计算图片选择数量 } @Override public void clearView() { //item删除后需要重新计算底部区域的显示位置,否则会造成底部区域显示混乱 fixBottom(); } });
4:注意需设置加号不能进行拖拽
由以上分析可知,ItemTouchHelper.Callback的isLongPressDragEnabled()可以设置是否支持长按拖拽,默认是true,即支持长按拖拽。现在我们要自定义指定哪些item可以拖拽,哪些不可以,因此我们需要重写isLongPressDragEnabled()
/** * 设置是否支持长按拖拽 * 此处必须返回false * 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽 */ @Override public boolean isLongPressDragEnabled() { return false; }
item的长按事件中进行是否拖动判断,这边的长按有两种方式,一种是在Adapter适配器中对item的长按事件进行监听回调,还有一种方式是自定义RecyclerView 的点击监听事件,可以参考以下内容:RecyclerView的高效触摸监听 - 简书,至于哪种方式看大家自己选择,这边用前者进行讲解。
主要就是在自己设置的长按回调里进行是否拖拽的操作!
@Override public void onItemLongClick(RecyclerView.ViewHolder viewHolder) { //我这边是因为当position==数据数量时就是加号图片,这里要看实际的值进行设置 if (viewHolder.getBindingAdapterPosition() != mDataList.size()) { mItemTouchHelper.startDrag(viewHolder);//viewHolder开始拖动 } }
5:在代码中计算底部区域的显示位置
/** * 处理recyclerView下面的布局 */ private void fixBottom() { int row = mAdapter.getItemCount() / 3; row = (0 == mAdapter.getItemCount() % 3) ? row : row + 1;//少于3为1行 int screenWidth = AnkoScreenKt.getScreenWidth(getContext()); int itemHeight = (screenWidth - AnkoDimensionsKt.dp2px(getContext(), 40)) / 3; int editHeight = mBinding.feedbackContentEdit.getHeight(); editHeight = editHeight == 0 ? AnkoDimensionsKt.dp2px(getContext(), 130) : mBinding.feedbackContentEdit.getHeight(); int layoutMargin = AnkoDimensionsKt.dp2px(getContext(), 20);//距离上部应用的间隔 int marginTop = itemHeight * row + editHeight + layoutMargin;//+ itemSpace * (row - 1) RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mBinding.bottomLl.getLayoutParams(); //用户判断 在每次fix底部布局高度后用来判断底部按钮的点击位置 注意要减去顶部edittext的高度 judgeClickMargin = marginTop - editHeight; params.setMargins(0, marginTop, 0, 0); mBinding.bottomLl.setLayoutParams(params); }
记得在页面初始化和对应item发生变化的方法里要调用此方法进行布局设置。
6:底部位置的点击(这点可以不考虑)
由于设置RecyclerView是高度设置是android:layout_height="wrap_content",这样RecyclerView就不会拦截底部布局的点击事件,所以点击操作只需要用id在代码中进行点击事件的判断即可,就不需要下面的这些操作了
由于RecyclerView是高度设置是android:layout_height="match_parent",在整个界面的内容没有超过屏幕时,点击布局其实监听进入的是RecyclerView的OnItemTouchListener监听,所以要进行两层点击判断
- 继承RecyclerView.OnItemTouchListener对里面的方法进行处理
import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.core.view.GestureDetectorCompat; import androidx.recyclerview.widget.RecyclerView; /** * RecyclerViewde 的点击 进行拖拽的布局会照成recyclerview下方的布局无法点击,需要在RecyclerView点击里重新设置 * <p> * 因为我们自带的适配器已近设置了item的点击和长按点击的功能,这边就把这2个点击屏蔽 * * @author Wuczh * @date 2021/12/1 */ public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener { private GestureDetectorCompat mGestureDetectorCompat; private RecyclerView mRecyclerView; public OnRecyclerItemClickListener(RecyclerView recyclerView) { mRecyclerView = recyclerView; mGestureDetectorCompat = new GestureDetectorCompat(mRecyclerView.getContext(), new ItemTouchHelperGestureListener()); } @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { mGestureDetectorCompat.onTouchEvent(e); return false; } @Override public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { mGestureDetectorCompat.onTouchEvent(e); } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } // public abstract void onItemClick(RecyclerView.ViewHolder viewHolder); // // public abstract void onLongClick(RecyclerView.ViewHolder viewHolder); public abstract void onOtherClick(MotionEvent e); private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (childViewUnder != null) { RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder); // onItemClick(childViewHolder); } else { onOtherClick(e); } return true; } @Override public void onLongPress(MotionEvent e) { View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); if (childViewUnder != null) { RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder); // onLongClick(childViewHolder); } } } }
在回调进行布局位置判断,设置对应点击
mBinding.picRv.addOnItemTouchListener(new OnRecyclerItemClickListener(mBinding.picRv) { @Override public void onOtherClick(MotionEvent e) { int bottomItemHeight = AnkoDimensionsKt.dp2px(getContext(), 50);//历史意见反馈的高度 if (e.getY()>judgeClickMargin) { int between=(int)e.getY()-judgeClickMargin;//判读触摸点与 bottom布局分界处的距离 int oneItem=(bottomItemHeight);//一个textview+一个分割线的高度 if (between>0 && between<=oneItem) { //点击在第一个textview上 ---所在位置 FeedBackHistoryActivity.start(getContext()); } // else if (between>oneItem && between<=2*oneItem) { // // //点击在第二个textview上 ---谁可以看 // ToastUtil.normal("谁可以看"); // } else if (between>2*oneItem && between<=3*oneItem) { // // //点击在第三个textview上 ---提醒谁看 // ToastUtil.normal("提醒谁看"); // } else if (between>3*oneItem && between<=4*oneItem && e.getX()>=leftMargin && e.getX()<=(starWidth+leftMargin)) { // //点击星星 同步到空间 // ToastUtil.normal("同步到空间"); // } } } });
- 当界面超过屏幕时,采用布局的id设置点击事件
//意见反馈 mBinding.feedbackHistoryTv.setOnClickListener(v -> FeedBackHistoryActivity.start(getContext()));
以上就是拖拽删除的主要操作
附上demo的guthub下载路径:GitHub - WCaiZhu/PictureDrag: 仿微信进行拍照、选择图片后进行图片拖拽删除
-
Android 实现图片相册选择+拍照,并在选中的图片右上角添加删除图标。
2021-11-30 16:19:27效果图: 一、在app的build中引入相应的框架 //recyclerview implementation "androidx.recyclerview:recyclerview:1.1.0" //glide implementation ...//导入相册多图片选择库 implementation .效果图:
一、在app的build中引入相应的框架
//recyclerview implementation "androidx.recyclerview:recyclerview:1.1.0" //glide implementation "com.github.bumptech.glide:glide:4.10.0" implementation "com.github.bumptech.glide:compiler:4.10.0" //导入相册多图片选择库 implementation 'com.github.donkingliang:ImageSelector:2.1.1' //压缩文件,压缩图片,压缩Bitmap implementation 'com.github.nanchen2251:CompressHelper:1.0.5'
2. 对图片进行压缩处理 ImageCompressUtil
package com.ruidde.csndresourcedemo.unilt; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; /** * 对图片进行压缩处理 */ public class ImageCompressUtil { /** * 通过压缩图片的尺寸来压缩图片大小,仅仅做了缩小,如果图片本身小于目标大小,不做放大操作 * * @param pathName 图片的完整路径 * @param targetWidth 缩放的目标宽度 * @param targetHeight 缩放的目标高度 * @return 缩放后的图片 */ public static Bitmap compressBySize(String pathName, int targetWidth, int targetHeight) { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; // 不去真的解析图片,只是获取图片的头部信息,包含宽高等; Bitmap bitmap = BitmapFactory.decodeFile(pathName, opts); // 得到图片的宽度、高度; int imgWidth = opts.outWidth; int imgHeight = opts.outHeight; // 分别计算图片宽度、高度与目标宽度、高度的比例;取大于等于该比例的最小整数; int widthRatio = (int) Math.ceil(imgWidth / (float) targetWidth); int heightRatio = (int) Math.ceil(imgHeight / (float) targetHeight); if (widthRatio > 1 || heightRatio > 1) { if (widthRatio > heightRatio) { opts.inSampleSize = widthRatio; } else { opts.inSampleSize = heightRatio; } } // 设置好缩放比例后,加载图片进内容; opts.inJustDecodeBounds = false; opts.inPreferredConfig = Bitmap.Config.RGB_565; opts.inDither = true; bitmap = BitmapFactory.decodeFile(pathName, opts); return bitmap; } }
3.选中图片的Adapter:PhotoImagsAdapter
package com.ruidde.csndresourcedemo; import android.content.Context; import android.graphics.Bitmap; import android.os.Environment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.nanchen.compresshelper.CompressHelper; import java.io.File; import java.util.ArrayList; import java.util.List; class PhotoImagsAdapter extends RecyclerView.Adapter<PhotoImagsAdapter.InfoImageHolder> { private Context context; private List<String> mDatas = new ArrayList<>(); public PhotoImagsAdapter(Context context, List<String> mDatas) { this.context = context; this.mDatas = mDatas; } public void setmDatas(List<String> mDatas) { this.mDatas = mDatas; notifyDataSetChanged(); } @NonNull @Override public PhotoImagsAdapter.InfoImageHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View v = LayoutInflater.from(context).inflate(R.layout.item_info_img, parent, false); return new InfoImageHolder(v); } @Override public void onBindViewHolder(@NonNull InfoImageHolder holder, final int position) { /** * 数据操作 */ if (mDatas != null && mDatas.size() > 0) { if (mDatas.size() == position) { holder.img_photo.setImageResource(R.mipmap.img_add); holder.btn_clearResource.setVisibility(View.GONE); } else { String dateStr = mDatas.get(position); holder.btn_clearResource.setVisibility(View.VISIBLE); /** * 在Activity里面使用 * dateStr 图片的绝对路径 * */ File oldFile = new File(dateStr); // File newFile = CompressHelper.getDefault(this).compressToFile(oldFile); // File newFile = new CompressHelper.Builder(context) // .setMaxWidth(360) // 默认最大宽度为720 // .setMaxHeight(480) // 默认最大高度为960 // .setQuality(80) // 默认压缩质量为80 // .setCompressFormat(Bitmap.CompressFormat.JPEG) // 设置默认压缩为jpg格式 // .setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory("Aruidde").getAbsolutePath()) //压缩图片所在路径 // .build() // .compressToFile(oldFile); /** * 但是不会存储压缩后的图片 * 压缩后的Bitmap * */ // Bitmap bitmap = CompressHelper.getDefault(context).compressToBitmap(oldFile); Bitmap bitmap = new CompressHelper.Builder(context) .setMaxWidth(360) // 默认最大宽度为720 .setMaxHeight(480) // 默认最大高度为960 .build() .compressToBitmap(oldFile); /** * 适合API 28 * 将图片压缩并转化为Bitmap * */ // Bitmap arrBitmap = ImageCompressUtil.compressBySize(dateStr, 300, 300); // Glide.with(context).load(newFile).into(holder.img_photo); Glide.with(context).load(bitmap).into(holder.img_photo); } } else { holder.img_photo.setImageResource(R.mipmap.img_add); holder.btn_clearResource.setVisibility(View.GONE); } //添加图片 holder.img_photo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { itemOnClickListener.onAddItenClick(position); } }); //删除图片 holder.btn_clearResource.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { itemOnClickListener.onDeleteItenClick(position); } }); } @Override public int getItemCount() { return mDatas != null ? (mDatas.size() + 1) : 1; } /** * 创建ViewHolder * 元素声明 */ class InfoImageHolder extends RecyclerView.ViewHolder { ImageView img_photo; ImageView btn_clearResource; RelativeLayout re_imgResourxe; public InfoImageHolder(@NonNull View itemView) { super(itemView); img_photo = itemView.findViewById(R.id.img_photo); btn_clearResource = itemView.findViewById(R.id.btn_clearResource); re_imgResourxe = itemView.findViewById(R.id.re_imgResourxe); } } //添加图片 private InfoImgClickListener itemOnClickListener; interface InfoImgClickListener { void onAddItenClick(int addPosition); void onDeleteItenClick(int deletePosition); } public void setAddImgClickLisitenner(InfoImgClickListener itemOnClickListener) { this.itemOnClickListener = itemOnClickListener; } }
4.布局xml
(1)。item_info_img
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <RelativeLayout android:id="@+id/re_imgResourxe" android:layout_centerVertical="true" android:visibility="visible" android:layout_width="235px" android:layout_height="235px"> <ImageView android:id="@+id/img_photo" android:layout_width="200px" android:layout_height="200px" android:layout_centerInParent="true" android:scaleType="fitXY" android:src="@mipmap/img_add"/> <ImageView android:id="@+id/btn_clearResource" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/img_exit" android:padding="10px" android:layout_marginTop="-10px" android:layout_marginRight="-10px" android:layout_alignParentRight="true"/> </RelativeLayout> </RelativeLayout>
(2)。主Activity布局
<androidx.recyclerview.widget.RecyclerView android:id="@+id/img_recycleview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:spanCount="4" tools:listitem="@layout/item_info_img"/>
5.最主要通过 FileProvider 创建共享文件
在res文件夹下创建xml文件并创建 file_paths.xml文件
<?xml version="1.0" encoding="utf-8"?> <paths> <paths> <external-path name="images" path="Pictures" /> <external-path path="jzFile" name="image" /> <external-files-path name="jzApp.apk" path="Download"/> </paths> </paths>
Mainfest文件中添加注册
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.ruidde.csndresourcedemo.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
-
百度网盘图片无法删除
2020-05-25 17:50:21百度网盘客户端图片选项里面是没有图片的,但是无法注销百度帐号,后来找到百度网盘网页版图片选项里面有一些灰白色的图片并且无法删除,我尝试过重新登录,清除网站数据,更换浏览器,结果百度网盘网页版还是有那些...百度网盘图片无法删除
百度网盘客户端图片选项里面是没有图片的,但是无法注销百度帐号,后来找到百度网盘网页版图片选项里面有一些灰白色的图片并且无法删除,我尝试过重新登录,清除网站数据,更换浏览器,结果百度网盘网页版还是有那些图片并且无法删除,我也问题反馈过,但是感觉没用,哪位大佬知道该怎么办吗?
-
小程序图片上传及预览和删除
2019-04-12 16:13:30其中wx:for="{{picArr}}"为所有的图片张数,src为img 的图片路径 ; 自定义data-src为当前点击的图片路劲(为预览做准备)自定义data-list为所有的图片张数(为预览做准备) <block wx:key="imgbox" wx:for="{{... -
利用微信的weui框架上传、预览和删除图片
2018-03-09 09:48:31jQuery WeUI 是专为微信公众账号开发而设计的一个框架,jQuery WeUI的官网:http://jqweui.com/需求:需要在微信公众号网页添加上传图片功能技术选型:实现上传图片功能可选百度的WebUploader、饿了么的Element和... -
MultipartFile类中的transferTo方法上传图片文件时,无法进行删除旧图片文件
2019-04-08 14:28:54项目中需要上传图片处理,项目框架使用的是Springmvc,其中查阅资料springmvc已经封装了MultipartFile类,其中的transferTo方法上传图片文件时,无法进行删除旧图片文件,故记录下解决问题途径 上传代码截图: ... -
Android GridView扩展仿微信微博发图动态添加删除图片
2016-11-04 14:29:35在平时的开发中,我们会看到不管是微信发朋友圈照片还是微博发布新鲜事,添加图片的时候都是选完后面还有个+号再去选择图片,这样的话比较方便用户去添加图片,有的右上角还有个...添加图片的+号图片 删除图片的图片 -
vue 渲染出来的图片右上角叉号点击删除
2018-09-05 16:46:26html部分 <span class="delete" @click="showFiguredelete(index)">×</span> js部分 showFiguredelete(index){ var that=this;... console.l... -
android仿小红书图片拖拽(改进版,仿微信朋友圈拖拽删除)
2019-04-11 13:38:51一、小红书效果 上面三个图是小红书发布动态的时候选择好图片后,长按图片进行排序的效果。长按后,选择的图片浮起,随手指左右移动,靠近左右边缘的...2.当图片满足9个的时候将最后位置的+号隐藏,小于9... -
css实现图片效果功能——圆角叉号删除键
2018-01-25 13:32:41要求是不使用图片,实现右上角删除键功能 实现流程: a. 使用输入法工具(比如搜狗输入法工具箱,也可以使用电脑自带的“字符映射表”)找到符号“ × ”; b. 在一个div块级元素中放置符号“ × ”,使用strong... -
前端图片上传功能,可删除,可限制上传张数,可限制图片的格式和大小,pc端移动端都通用。原生无依赖。
2018-07-24 15:42:34<div class="item"...上传项目图片</div> <div class="contain_upload"> <div class="tip">包括但不限于:项目效果 -
LaTeX技巧:去掉图片默认标题中的编号‘:’号
2020-08-27 16:12:23这里更改图片默认编号Figure 2:为Fig 1.: ** 提示: #程序如下: \begin{figure}[htbp] \begin{center} \subfigure[Positions trajectories and errors]{ \includegraphics[width=0.47\... -
删除microsoft_如何完全删除您的Microsoft帐户
2020-09-12 18:12:54删除microsoftIf you only pay attention to the headlines that Microsoft wants you to keep your eye on, you’d be forgiven for thinking that Windows 10 has been a universal success. So far, Redmond’s ... -
Activiti流程定义部署、删除以及查看流程图片
2018-06-25 15:45:50如果当前流程引擎中已存在相同key的流程,则找到当前key对应的最高版本号,在最高版本号上加1 */ public void queryProcessDefinition() throws Exception { // 获取仓库服务对象 RepositoryService ... -
【java】删除上传到服务器的临时文件
2018-10-17 18:11:33文章目录删除上传到服务器的临时文件前言说明正文目的背景代码第一种实现方式第二种实现方式测试结果 删除上传到服务器的临时文件 前言 &... -
2022年最新微博批量删除代码_自动化删除新浪微博代码
2022-03-20 20:05:28新浪微博本身不提供批量删除微博的方法,而有些时候,我们需要删除自己微博上所有带图片的内容,比如最近富二代王思聪就清空了自己的微博。 下面就是一个批量删除微博的方法,包括带图片微博的方法。 先使用谷歌... -
安卓系统目录说明大全,各文件夹是什么,可以删除吗?(以MIUI为例)
2021-12-16 16:21:43.com.pingan.paces.ccms 平安口袋银行(旧版目录,可删除) 新版本目录已移至Andriod的data目录下,所以可以放心删除。 PASpeechSDK 平安的某个SDK(可删除) 目前没有发现用处,该目录为空,可删除。 -
【Unity3D日常开发】Unity3D中实现向Web服务器上传图片以及下载图片功能
2022-01-07 09:53:11今天分享一下从搭建web服务器,到向服务器发送图片,以及加载图片的整体实现。 因为是Demo演示,所以尽可能的简单、详细且实用,有什么错误敬请指正。 先看一下效果图: 文章参考:Unity向Web服务器上传和下载图片 ... -
WPS删除多余插件
2021-07-29 11:34:45以macOS 为例 ...5.选择不用的插件,例如【云文档】(对应的是【kclouddocs】文件夹),进行删除 6.像在wps 经常出现的在线字体,在这里就可以直接移除掉 移除前 移除后,清爽很多 ... -
iOS开发者账号、证书的区别以及证书被误删除
2016-07-21 10:53:17新手或者老手,有没有手欠的revoke过Member Center中的各种证书,删除过各种mobileprovision文件?一、个人证书、公司证书和企业证书的区别苹果对开发者主要分为3类:个人、组织(公司、企业)、教育机构。即: 1、... -
Vue+Element UI Upload 组件 上传单张图片去除后面的+号
2019-12-03 15:08:10这个思路是隐藏 el-upload–picture-card” ...这样在上传中那个"+"号也可以马上隐藏 上传组件代码块 <el-form-item label="产品图片:" size="mini"> <el-upload action="上传接口" :limit="1" :on-pr... -
layui实现单个删除和批量删除
2019-12-05 16:32:17文章目录layui实现单个删除和批量删除先获得表格数据一、单个删除1、在js文件中绑定操作工具栏的删除操作2、domain层:3、controller层:4、service层:5、dao层:二、批量删除1、首先在页面上定义批量删除按钮2、在... -
图片验证码是如何刁难用户的?一招教你彻底去除图片验证码!
2021-02-02 14:11:03图片验证码是验证码的一种,图片验证码常见的形式有输入图片中字母、数字等,目前的发展更为多元。下面小编从多方面跟大家讲讲图片验证码。 ➤验证码的前世今生 全自动区分计算机和人类的图灵测试(Completely ... -
Java实现阿里OSS上传和删除
2019-01-13 09:26:57依赖 <!-- httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient&.../version& -
Excel自动插入jpg图片或png图片 VBA 工具 模块
2020-08-14 16:11:58在工作中,经常需要将一个商品表格插入商品的图片,比如在鞋服箱包等行业,那是经常要用到Excel中的商品需要插入商品的图片,如果一个个的插入,那会疯掉的,所以根据自己所在的鞋业行业,和借鉴了前辈的部分代码,... -
twitter账户受限_如何删除您的Twitter帐户
2020-09-25 13:38:36您可以永久删除您的帐户,删除所有推文,关注者,收藏夹和其他数据。 它几乎会立即从Twitter消失,并将在30天之内从Twitter的服务器上完全擦除。 停用方式 (How Deactivation Works) Visit the Twitter website in ... -
QQ群主怎么清理群里的不良内容(怎么一次性全部删除QQ群不良信息)
2020-07-10 18:42:45群主和发送信息者可以删除漫游的聊天记录,删除后,其他群成员将不能看到被删除的聊天内容。 二、群空间论坛或是群相册有不良信息: 可联系群主或群管理员可以对论坛或相册进行删除操作。若群主或管理员不愿意操作... -
flutter 长按弹出菜单,删除,分享,更多,撤回.
2020-01-28 16:44:11最近在开发IM软件,需要做:长按弹出菜单,删除,分享,更多,撤回. 先看看效果是不是你想要的 使用处的代码 _menuView() { bool isCanRecall = DateTime.now().millisecondsSinceEpoch - dateTime.... -
mui图片上传
2019-05-11 14:26:12作为一个后台开发人员、写这个真的是浪费不少脑细胞,虽然做出来了,但是也很麻烦,如果哪位大哥或者小妹妹那里有简单点的,我跪求!...点击那个加号(注:这个 + (加号)是图标不是图片,不会弄的找前端的给弄)会...