English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
في العطلة نهاية الأسبوع، قمت بتجميع معلومات حول SwipeMenuListView (قائمة تمرير) لـ Android، إليك ما تم جمعه:
SwipeMenuListView (قائمة تمرير)
قائمة تمرير للListView.-- مشروع مفتوح المصدر رائع جدًا.
مثال
الجزء الأول: مقدمة
مررت وقتًا طويلاً في دراسة العناصر المخصصة وتوزيع الأحداث، وأردت العثور على مشروع لتدريب، ويبدو أن هذا يبرهن على ما تعلمته.
وجدت هذا المشروع على GitHub: SwipeMenuListView هذا رائع حقًا، يوفر إلهامًا كبيرًا في توزيع الأحداث وتصميم العناصر المخصصة، رغم أن هناك بعض العيوب الصغيرة، سيتم شرحها لاحقًا. إذا كنت ترغب في معرفة كيفية تنفيذ قائمة التمرير، هذا المقال سيساعدك بالتأكيد، حيث يتم تحليل كل ملف من منظور واسع ومحدود.
عنوان المشروع: https://github.com/baoyongzhang/SwipeMenuListView/tree/b00e0fe8c2b8d6f08607bfe2ab390c7cee8df274 إصدار: b00e0fe الاستخدام بسيط جداً، فقط ثلاث خطوات، يمكنك فهمه على GitHub دون استهلاك مساحة، هذا المقال يركز فقط على التحليل النظري. بالإضافة إلى ذلك، إذا شعرت بأنك لا تتفق معي في الرؤية، وكانت الرؤية صعبة، يمكنك الرجوع إلى ما أضفته من التعليقات: http://download.csdn.net/detail/jycboy/9667699
أنظر أولاً إلى الصورتين: للحصول على فكرة عامة
هذه هي جميع الكلاسيين في الإطار.
1. في الصورة التالية هي مستوى الرؤية:
في الصورة أعلاه: SwipeMenuLayout هو تصميم ListView للعناصر، يتكون من جزأين، الجزء الأول هو contentView الذي يتم عرضه بشكل طبيعي، والجزء الآخر هو menuView الذي يتم سحبه؛ SwipeMenuView التي تنحدر من LinearLayout، عند إضافة view، يتم إضافةها أفقياً، يمكن إضافة عدة views أفقياً.
2. في الصورة التالية هي بنية الرسم البياني للكلاسيين:
في الصورة أعلاه، يظهر العلاقة بين الكلاسيين، ويتم تسمية دور كل كلاسيء بجانبه.
الجزء الثاني: تحليل المصدر
SwipeMenu و SwipeMenuItem كلاسيين ماديين، يتم تعريف الخصائص وطرق setter و getter، انظر فقط. بشكل عام، التعليقات في المصدر واضحة.
2.1 SwipeMenuView: التعليقات في الكود واضحة جداً
/** * LinearLayout أفقي، وهو تصميم الواجهة الأم لـ swipemenu * يحدد بشكل رئيسي طرق إضافة Item وضبط خصائص Item * @author baoyz * @date 2014-8-23 * */ public class SwipeMenuView extends LinearLayout implements OnClickListener { private SwipeMenuListView mListView; private SwipeMenuLayout mLayout; private SwipeMenu mMenu; private OnSwipeItemClickListener onItemClickListener; private int position; public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } public SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView) { super(menu.getContext()); mListView = listView; mMenu = menu; // // قائمة MenuItem List<SwipeMenuItem> items = menu.getMenuItems(); int id = 0; // من خلال item بناء View وإضافته إلى SwipeMenuView for (SwipeMenuItem item : items) { addItem(item, id++); } } /** * تحويل MenuItem إلى عنصر واجهة مستخدم،item واحد يعادل LinearLayout عمودي، * SwipeMenuView هو LinearLayout أفقي، */ private void addItem(SwipeMenuItem item, int id) { //布局参数 LayoutParams params = new LayoutParams(item.getWidth(), LayoutParams.MATCH_PARENT); LinearLayout parent = new LinearLayout(getContext()); //ضبط معرف عنصر القائمة، للاستخدام في الأحداث النقرية اللاحقة parent.setId(id); parent.setGravity(Gravity.CENTER); parent.setOrientation(LinearLayout.VERTICAL); parent.setLayoutParams(params); parent.setBackgroundDrawable(item.getBackground()); //ضبط المستمع parent.setOnClickListener(this); addView(parent); //إضافة إلى SwipeMenuView، أفقيًا if (item.getIcon() != null) { parent.addView(createIcon(item)); } if (!TextUtils.isEmpty(item.getTitle())) { parent.addView(createTitle(item)); } } //إنشاء img private ImageView createIcon(SwipeMenuItem item) { ImageView iv = new ImageView(getContext()); iv.setImageDrawable(item.getIcon()); return iv; } /*حسب المعامل إنشاء العنوان*/ */ private TextView createTitle(SwipeMenuItem item) { TextView tv = new TextView(getContext()); tv.setText(item.getTitle()); tv.setGravity(Gravity.CENTER); tv.setTextSize(item.getTitleSize()); tv.setTextColor(item.getTitleColor()); return tv; } @Override /** * يتم استخدام mLayout المرسل للتحقق من ما إذا كان مفتوحًا * يتم استدعاء حادثة النقر onItemClick */ public void onClick(View v) { if (onItemClickListener != null && mLayout.isOpen()) { onItemClickListener.onItemClick(this, mMenu, v.getId()); } } public OnSwipeItemClickListener getOnSwipeItemClickListener() { return onItemClickListener; } /** * تعيين حادثة النقر للitem * @param onItemClickListener */ public void setOnSwipeItemClickListener(OnSwipeItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } public void setLayout(SwipeMenuLayout mLayout) { this.mLayout = mLayout; } /** * واجهة التدفق المعدل للحادثة النقر */ public static interface OnSwipeItemClickListener { /** * يتم استدعاء onItemClick في الحدث onClick * @param view التخطيط الأب * @param menu كائن menu * @param index id من menuItem */ void onItemClick(SwipeMenuView view, SwipeMenu menu, int index); } }
**SwipeMenuView هو View الذي يظهر عند التمرير، انظر إلى صيغة إنشاء SwipeMenuView(SwipeMenu menu, SwipeMenuListView listView)؛ مرور على Items: menu.getMenuItems(); إضافة item إلى SwipeMenuView باستخدام طريقة addItem.
في طريقة addItem: كل item هي LinearLayout.
2.2 SwipeMenuLayout:}
كود هذا الكائن طويل قليلاً، لذا سنعده لثلاثة أجزاء ونرى فقط الكود الأساسي، يجب أن تكون الفكرة واضحة بعد ذلك.
public class SwipeMenuLayout extends FrameLayout { private static final int CONTENT_VIEW_ID = 1; private static final int MENU_VIEW_ID = 2; private static final int STATE_CLOSE = 0; private static final int STATE_OPEN = 1; //اتجاه private int mSwipeDirection; private View mContentView; private SwipeMenuView mMenuView; 。。。。。 public SwipeMenuLayout(View contentView, SwipeMenuView menuView) { this(contentView, menuView, null, null); } public SwipeMenuLayout(View contentView, SwipeMenuView menuView, Interpolator closeInterpolator, Interpolator openInterpolator) { super(contentView.getContext()); mCloseInterpolator = closeInterpolator; mOpenInterpolator = openInterpolator; mContentView = contentView; mMenuView = menuView; //تحديد SwipeMenuLayout كـ SwipeMenuView للتحقق مما إذا كان مفتوحًا mMenuView.setLayout(this); init(); } private void init() { setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); المستمع لل手势 = جديد SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { isFling = false; return true; } @Override //velocityX هذا المعامل هو السرعة في اتجاه المحور الأفقي، يساراً هو سالب، يميناً هو إيجابي public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // TODO if (Math.abs(e1.getX() - e2.getX()) > MIN_FLING && velocityX < MAX_VELOCITYX) { isFling = true; } Log.i("tag","isFling="+isFling+" e1.getX()="+e1.getX()+" e2.getX()="+e2.getX()+ " velocityX="+velocityX+" MAX_VELOCITYX="+MAX_VELOCITYX); // Log.i("byz", MAX_VELOCITYX + ", velocityX = " + velocityX); return super.onFling(e1, e2, velocityX, velocityY); } }); mGestureDetector = new GestureDetectorCompat(getContext(), mGestureListener); 。。。。 LayoutParams contentParams = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mContentView.setLayoutParams(contentParams); if (mContentView.getId() < 1) { //noinspection ResourceType mContentView.setId(CONTENT_VIEW_ID); } //noinspection ResourceType mMenuView.setId(MENU_VIEW_ID); mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); addView(mContentView); addView(mMenuView); }
من الممكن رؤية أن SwipeMenuLayout يتكون من جزأين، هما item View الخاص بالمستخدم و menu View. عملية السحب باستخدام الأصابع تتم من خلال SimpleOnGestureListener.
/** * 滑动事件,用于外边调用的接口 * 这是一个对外暴露的API,而调用这个API的是SwipeMenuListView,那么MotionEvent是SwipeMenuListView的MotionEvent * @param event * @return */ public boolean onSwipe(MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction()) { حالة MotionEvent.ACTION_DOWN: mDownX = (int) event.getX();//记下点击的x坐标 isFling = false; break; case MotionEvent.ACTION_MOVE: // Log.i("byz", "downX = " + mDownX + ", moveX = " + event.getX()); int dis = (int) (mDownX - event.getX()); if (state == STATE_OPEN) {//当状态是open时,dis就是0 Log.i("tag", "dis = " + dis);//这个值一直是0 //DIRECTION_LEFT = 1 || DIRECTION_RIGHT = -1 dis += mMenuView.getWidth()*mSwipeDirection;//mSwipeDirection=1 Log.i("tag", "dis = " + dis + ", mSwipeDirection = " + mSwipeDirection); } Log.i("tag", "ACTION_MOVE downX = " + mDownX + ", moveX = " + event.getX()+", dis="+dis); swipe(dis); break; case MotionEvent.ACTION_UP: // التحقق من مسافة السحب، ما إذا كان للفتح أو الإغلاق // هنا، إذا كان هناك عنصر مفتوحًا بالفعل، عند ذلك السحب على عنصر آخر، ما هو تحسين هذا الأسلوب؟ if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) && Math.signum(mDownX - event.getX()) == mSwipeDirection) { Log.i("tag", "ACTION_UP downX = " + mDownX + ", moveX = " + event.getX()); // فتح smoothOpenMenu(); } آخره { // إغلاق smoothCloseMenu(); return false; } break; } return true; } public boolean isOpen() { return state == STATE_OPEN; } /** * مسافة السحب dis، تمرر محتوى العرض المطلق و mMenuView مسافة dis * @param dis */ private void swipe(int dis) { إذا (!mSwipEnable) { الرجوع; } // left هو إيجابي؛right هو سالب if (Math.signum(dis) != mSwipeDirection) { // left=1;right =-1 dis = 0; // لا تمرر } else if (Math.abs(dis) > mMenuView.getWidth()) { // أكبر من عرضه، dis هو عرض mMenuView.getWidth() dis = mMenuView.getWidth() * mSwipeDirection; } // إعادة تعيين التخطيط، التحرك إلى اليسار (أو إلى اليمين)، mContentView.layout(-dis, mContentView.getTop(), محتوى العرض المطلق().getwidth() - dis, getmeasuredheight()); إذا (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {//1 //كما كان، إعادة تعيين تخطيط menuview، الرسم واضح mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(), mContentView.getWidth() + mMenuView.getWidth() - dis, mMenuView.getBottom()); } آخره { mMenuView.layout(-mMenuView.getWidth() - dis, mMenuView.getTop(), - dis, mMenuView.getBottom()); } } /** * تحديث الوضع state = STATE_CLOSE; * إغلاق القائمة */ public void smoothCloseMenu() { الوضع = STATE_CLOSE; إذا (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mBaseX = -mContentView.getLeft(); //التحرك بمسافة mMenuView.getWidth()، يخفي المكون بالكامل mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350); } آخره { mBaseX = mMenuView.getRight(); mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350); } postInvalidate(); } public void smoothOpenMenu() { إذا (!mSwipEnable) { الرجوع; } الوضع = STATE_OPEN; إذا (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); Log.i("tag","mContentView.getLeft()="+mContentView.getLeft()+", mMenuView="+mMenuView.getWidth());//-451، هو مسافة التحركdis، - (downX-moveX) //مطلق mContentView.getLeft() = -540، مطلق mMenuView = 540، هذان المطلقان متساويان، تمامًا صحيح! هههه· } آخره { mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); } //تتم إدعاء هذه الطريقة في thread غير thread UI، لجعل العنصر يُعدل. postInvalidate(); } 。。。 }
الطرق الرئيسية هي onSwipe و swipe هذان الطريقتان، واللógica الرئيسية هي: onSwipe هو API مفتوح للإستدعاء من الخارج،
تم استدعاء onSwipe في طريقة معالجة أحداث onTouchEvent في SwipeMenuListView، بينما swipe هو تمرير mContentView و mMenuView بمسافة dis.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //عرضه غير محدود، طوله محدد mMenuView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY)); } protected void onLayout(boolean changed, int l, int t, int r, int b) { mContentView.layout(0, 0, getMeasuredWidth(), mContentView.getMeasuredHeight()); if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) { //التمرير إلى اليسار //على أساس العنصر الأب، مع اليسار واليمنى كمرجع، مخفي في اليمين mMenuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + mMenuView.getMeasuredWidth(), mContentView.getMeasuredHeight()); } else { //تمرير إلى اليمين، إخفاء في الجانب الأيسر mMenuView.layout(-mMenuView.getMeasuredWidth(), 0, 0, mContentView.getMeasuredHeight()); } }
أسفل طرق onMeasure و onLayout هي الطرق التي يتم كتابتها في العناصر المخصصة غالبًا، حيث يتم قياس حجم العنصر في onMeasure ويتم إعداد نوع العرض على غير محدد يمكنه التوسع بلا حدود. على Layout يتم وضع العنصر بعد قياس حجمه في موقع ما في المخطط الوالدي، يمكن ملاحظة في الكود أن يتم إخفاء menuView في الجانب الأيسر (أو الأيمن) بناءً على اتجاه التمرير.
2.3 SwipeMenuAdapter
public class SwipeMenuAdapter implements WrapperListAdapter, OnSwipeItemClickListener { private ListAdapter mAdapter; private Context mContext; private SwipeMenuListView.OnMenuItemClickListener onMenuItemClickListener; public SwipeMenuAdapter(Context context, ListAdapter adapter) { mAdapter = adapter; mContext = context; } 。。。。 /** * إضافة قائمة عرض عند التمرير * يمكن ملاحظة هنا أن كل عنصر هو SwipeMenuLayout */ public View getView(int position, View convertView, ViewGroup parent) { SwipeMenuLayout layout = null; if (convertView == null) { View contentView = mAdapter.getView(position, convertView, parent);//رؤية الصفحة للعنصر SwipeMenu menu = new SwipeMenu(mContext); //إنشاء SwipeMenu menu.setViewType(getItemViewType(position)); createMenu(menu); // اختباري، يمكنك تجاهله أولاً SwipeMenuView menuView = new SwipeMenuView(menu, (SwipeMenuListView) parent); menuView.setOnSwipeItemClickListener(this); SwipeMenuListView listView = (SwipeMenuListView) parent; layout = new SwipeMenuLayout(contentView, menuView, listView.getCloseInterpolator(), listView.getOpenInterpolator()); layout.setPosition(position); } آخره { layout = (SwipeMenuLayout) convertView; layout.closeMenu(); layout.setPosition(position); View view = mAdapter.getView(position, layout.getContentView(), parent); } إذا (mAdapter instanceof BaseSwipListAdapter) { boolean swipEnable = (((BaseSwipListAdapter) mAdapter).getSwipEnableByPosition(position)); layout.setSwipEnable(swipEnable); } return layout; } // هذه الطريقة في وقت إنشاء هذا الكائن، يتم تعديله، هنا هي اختبارية، يمكنك تجاهلها. public void createMenu(SwipeMenu menu) { // Test Code 。。。。。。 } /** * مروحة swipeItemClickListener اللفظي * هذا النوع من الطريقة في وقت إنشاء هذا الكائن، يتم تعديله. */ public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { إذا (onMenuItemClickListener != null) { onMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index); } } 。。。。//تم تجاهل الأجزاء غير المهمة }
2.4 فئة رئيسية: SwipeMenuListview،
هذا الكود طويل، يحتاج إلى صبر عند النظر فيه.
public class SwipeMenuListView extends ListView { private static final int TOUCH_STATE_NONE = 0; private static final int TOUCH_STATE_X = 1; private static final int TOUCH_STATE_Y = 2; public static final int DIRECTION_LEFT = 1; //اتجاه public static final int DIRECTION_RIGHT = -1; private int mDirection = 1;//السحب من اليمين إلى اليسار بشكل افتراضي private int MAX_Y = 5; private int MAX_X = 3; private float mDownX; private float mDownY; private int mTouchState; private int mTouchPosition; private SwipeMenuLayout mTouchView; private OnSwipeListener mOnSwipeListener; //إنشاء item من قائمة المENU private SwipeMenuCreator mMenuCreator; //حدث ضغط على item من قائمة المENU private OnMenuItemClickListener mOnMenuItemClickListener; private OnMenuStateChangeListener mOnMenuStateChangeListener; private Interpolator mCloseInterpolator; //معدل تغيير الرسوم المتحركة private Interpolator mOpenInterpolator; //----إضافة ذاتية--هذه السطرين أضفتهما بنفسي، // إذا كنت تستطيع تنفيذ الشيفرة التالية في الدemo، فإنك ستجد أن عند سحب عنصر، إذا تم سحب عنصر آخر، فإن العنصر المفتوح سابقًا لم يغلق، يمكنك الرؤية في QQ السحب الجانبي، حيث يتم إغلاقه، لقد قمت بتعديل هذا قليلاً. private int mOldTouchPosition = -1; private boolean shouldCloseMenu; //-------- public SwipeMenuListView(Context context) { super(context); init(); } public SwipeMenuListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public SwipeMenuListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } //初始化变量 private void init() { MAX_X = dp2px(MAX_X); MAX_Y = dp2px(MAX_Y); موقع_الحالة_لمس = حالة_لمس_لاشيء; } @Override /** * 包装参数adapter为SwipeMenuAdapter */ public void setAdapter(ListAdapter adapter) { super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) { @Override public void createMenu(SwipeMenu menu) { if (mMenuCreator != null) { mMenuCreator.create(menu); } } @Override public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) { boolean flag = false; if (mOnMenuItemClickListener != null) { flag = mOnMenuItemClickListener.onMenuItemClick( view.getPosition(), menu, index); } //النقر مرة أخرى على item في list لإغلاق menu إذا (mTouchView != null && !flag) { mTouchView.smoothCloseMenu(); } } }); } 。。。。。 @Override //استقبال الحدث، تحديد ما إذا كان الحدث هو حدث نقر أو حدث سحب public boolean onInterceptTouchEvent(MotionEvent ev) { //معالجة في المكان الممنوع، في مكان تعيين الحدث عند السحب يمكن swip، عند النقر لا يؤثر على الحدث النقر الأصلي حالة_العملية = ev.getAction(); تحول (الحالة_العملية) { حالة MotionEvent.ACTION_DOWN: موقع_الأسفل_اليسرى = ev.getX(); موقع_الأسفل_اليمين = ev.getY(); boolean handled = super.onInterceptTouchEvent(ev); mTouchState = TOUCH_STATE_NONE; //تغيير الحالة إلى لا شيء في كل مرة Down //عودة position item mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); //الحصول على view الم对应 إلى item النقر، وهو SwipeMenuLayout View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); //يتم تعيين القيمة فقط عند الوجهة الفارغة لتجنب تعيين القيمة في كل مرة يتم لمسها، مما يؤدي إلى وجود عدة حالات مفتوحة if (view instanceof SwipeMenuLayout) { //إذا كانت مفتوحة، استقبال .mTouchView هو SwipeMenuLayout //إذا كانت المرة الأولى هي mTouchView، تحديث mTouchView؛ وإذا كانت ليست view، استقبال عودة true إذا (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) { Log.i("tag","Listview中的onInterceptTouchEvent ACTION_DOWN."); return true; } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); //الافتراضي هو left=1 } //إذا كانت اللمس على view آخر، استقبال هذا الحدث إذا (mTouchView != null && mTouchView.isOpen() && view != mTouchView) { handled = true; } إذا (mTouchView != null) { mTouchView.onSwipe(ev); } return handled; حالة MotionEvent.ACTION_MOVE: //استقبال الحدث عند الحركة، يتم معالجته في onTouch float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); إذا (مطلق(dy) > MAX_Y || مطلق(dx) > MAX_X) { // يتم تعيين حالة_اللمس إلى TOUCH_STATE_NONE في كل مرة يتم فيها تمرير down، فقط إذا تم إرجاع true سيتم انتقاله إلى onTouchEvent لذا يكفي كتابته هنا إذا (حالة_الحالة_لمس == حالة_لمس_لاشيء) { إذا (مطلق(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } آخره إذا (dx > MAX_X) { mTouchState = TOUCH_STATE_X; إذا (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } return true; } } العمليات_الجميلة @Override } @Override العمليات_الجميلة onTouchEvent(MotionEvent ev) { إذا (حالة_العملية != MotionEvent.ACTION_DOWN && عرض_لمس == null) return super.onTouchEvent(ev); حالة_العملية = ev.getAction(); تحول (الحالة_العملية) { حالة_لمس = MotionEvent.ACTION_DOWN: // شرط هذه الحالة_لمس هو أن الحدث قد تم تمريره بالفعل، لذا فإن الإحتمالات المحتملة هي: 1. تم سحب القائمة، ثم تم النقر على منطقة عنصر اليسرى // عند سحب القائمة، تم النقر على عنصر آخر // عند سحب العنصر، يحدث أولاً DOWN ثم MOVE Log.i("tag","Listview中的onTouchEvent ACTION_DOWN。هل تم النقر على عنصر آخر"); رقم_القديم = موقع_لمس; // لم يتم تصميم هذا بشكل معقول، يتم استدعاء هذا الحدث مباشرة بعد onInterceptTouchEvent، وسيكون موقع_لمس نفسه إذا (الموقع_القديم_لمس == -1) { // -1 هو القيمة الأصلية mOldTouchPosition = mTouchPosition; } موقع_الأسفل_اليسرى = ev.getX(); موقع_الأسفل_اليمين = ev.getY(); موقع_الحالة_لمس = حالة_لمس_لاشيء; موقع_لمس = pointToPosition((إنت) ev.getX(), (إنت) ev.getY()); // في القائمة // تم تعديل هذا، لم يعد يستخدم pldPos، بل أصبح الموقع_القديم_لمس إذا (موقع_لمس == الموقع_القديم_لمس && عرض_لمس != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; // اتجاه x (عنصر أفقي) // يتم استدعاء واجهة الأحداث onSwipe() لـ SwipeMenuLayout mTouchView.onSwipe(ev); Log.i("tag","Listview中的onTouchEvent ACTION_DOWN. تم التحريك أو النقر على عنصر آخر"); return true; } if(mOldTouchPosition != mTouchPosition){ // عندما يكون موقع الضغط المختلف // shouldCloseMenu = true; mOldTouchPosition = mTouchPosition; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); // تم فتح قائمة واحدة بالفعل، إذا تم النقر على عنصر آخر في هذه اللحظة // هذه الطريقة لن تتمكن أبدًا من التنفيذ! if (mTouchView != null && mTouchView.isOpen()) { //إغلاق swipeMenu mTouchView.smoothCloseMenu(); mTouchView = null; // return super.onTouchEvent(ev); // محاولة إلغاء حدث اللمس MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); // إلغاء الحدث، ينتهي الوقت // تنفيذ العودة على إغلاق menu if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; } if (view instanceof SwipeMenuLayout) { mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); } إذا (mTouchView != null) { mTouchView.onSwipe(ev); } break; case MotionEvent.ACTION_MOVE: // قد يكون هناك header، لذا يجب إزالة header للتحقق mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount(); // إذا تم التحريك مرة واحدة ولم يتم عرضه بشكل كامل، يتم سحبه مرة أخرى، في هذه الحالة تم تعيين mTouchView، لا يمكن التحريك إلى view أخرى غير قابلة للتحريك // قد يؤدي إلى mTouchView swip. لذا يجب استخدام التحقق من الموقع لتحديد ما إذا كان التحريك هو view if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) { break; } float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (mTouchState == TOUCH_STATE_X) { // إذا كان الاتجاه X إذا (mTouchView != null) { mTouchView.onSwipe(ev); // استدعاء أحداث التحريك } getSelector().setState(new int[]{0}); ev.setAction(الإجراء_بالمساحة_اللمس.الإلغاء); super.onTouchEvent(ev); //إجراء_الحدث_النهاية return true; } إذا (مطلق(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } آخره إذا (dx > MAX_X) { mTouchState = TOUCH_STATE_X; إذا (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; حالة MotionEvent.ACTION_UP: //إغلاق_menu Log.i("tag","حدث_اللمس ACTION_UP"); إذا (mTouchState == TOUCH_STATE_X) { إذا (mTouchView != null) { Log.i("tag","لماذا لم يغلق ACTION_UP الحدث_اللمس"); boolean isBeforeOpen = mTouchView.isOpen(); //إجراء_السحب_الحدث mTouchView.onSwipe(ev); boolean isAfterOpen = mTouchView.isOpen(); إذا (isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener != null) { إذا (isAfterOpen) { mOnMenuStateChangeListener.onMenuOpen(mTouchPosition); } آخره { mOnMenuStateChangeListener.onMenuClose(mTouchPosition); } } إذا (!isAfterOpen) { mTouchPosition = -1; mTouchView = null; } } إذا (mOnSwipeListener != null) { //إجراء_السحب_النهاية_الإشارة mOnSwipeListener.onSwipeEnd(mTouchPosition); } ev.setAction(الإجراء_بالمساحة_اللمس.الإلغاء); super.onTouchEvent(ev); return true; } break; } return super.onTouchEvent(ev); } public void smoothOpenMenu(int position) { if (position >= getFirstVisiblePosition() && position <= getLastVisiblePosition()) { View view = getChildAt(position - getFirstVisiblePosition()); if (view instanceof SwipeMenuLayout) { mTouchPosition = position; if (mTouchView != null && mTouchView.isOpen()) { mTouchView.smoothCloseMenu(); } mTouchView = (SwipeMenuLayout) view; mTouchView.setSwipeDirection(mDirection); mTouchView.smoothOpenMenu(); } } } /** * يمكن الذهاب إلى المصدر لرؤية الكود، وهو تحويل وحدات مختلفة إلى بكسل px، هنا dp->px * @param dp * @return */ private int dp2px(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); } public static interface OnMenuItemClickListener { boolean علىMenuItemClick(int position, SwipeMenu menu, int index); } public static interface OnSwipeListener { void علىSwipeStart(int position); void علىSwipeEnd(int position); } public static interface OnMenuStateChangeListener { void علىMenuفتح(int position); void onMenuClose(int position); } 。。。。 }
أهم لógica في هذا الكائن هي حول الحكم على الأحداث وتوزيعها، متى يمنع الحدث، ما هي العمليات المختلفة للحدث. إذا لم تكن على دراية بتوزيع الأحداث، يمكنك البحث عن المدونات المتعلقة بذلك على الإنترنت أو قراءة مدوناتي اللاحقة، يجب أن تكون هذه الأمور في الأيام القليلة القادمة.
في هذا المكان يتم تحليل لógica توزيع أحداث SwipeMenuListView: وهي معالجة أحداث النقر والتمرير للعناصر في SwipeMenuListView. عند التمرير، يمنع SwipeMenuListView الحدث ويقوم بمعالجته بنفسه، تذكر هذه اللógica لأنها واضحة في الكود. في الأسفل رسمت مخطط توزيع الأحداث:
حدث اللمس هو سلسلة من الأحداث: ACTION_DOWN->ACTION_MOVE->....ACTION_MOVE->ACTION_UP. تبدأ بACTION_DOWN وتنتهي بACTION_UP.
الأسفل هو طريقي للطباعة (أضف log في الكود الخاص بي)
I/tag: Listview中的onInterceptTouchEvent ACTION_DOWN。view=class com.baoyz.swipemenulistview.SwipeMenuLayout I/tag: onInterceptTouchEvent ACTION_DOWN handled=false I/tag: SwipeMenuLayout onTouchEvent I/tag: Listview中的onTouchEvent ACTION_DOWN。是否点击了另一个item I/tag: oldPos=1 mTouchPosition=1 I/tag: ACTION_MOVE downX = 987, moveX = 906.69666, dis=80 I/tag: ACTION_MOVE downX = 987, moveX = 855.5785, dis=131 I/tag: ACTION_MOVE downX = 987, moveX = 797.6258, dis=189 I/tag: ACTION_MOVE downX = 987, moveX = 735.9639, dis=251 I/tag: ACTION_MOVE downX = 987, moveX = 666.5104, dis=320 I/tag: ACTION_MOVE downX = 987, moveX = 589.0626, dis=397 I/tag: ACTION_MOVE downX = 987, moveX = 509.15567, dis=477 I/tag: ACTION_MOVE downX = 987, moveX = 431.7224, dis=555 I/tag: ACTION_MOVE downX = 987, moveX = 361.2613, dis=625 I/tag: ACTION_MOVE downX = 987, moveX = 319.70398, dis=667 I/tag: ACTION_UP onTouchEvent I/tag: لماذا لم يُغلق في ACTION_UP onTouchEvent I/tag: isFling=true e1.getX()=987.08606 e2.getX()=319.70398 velocityX=-4122.911 MAX_VELOCITYX=-1500 I/tag: ACTION_UP downX = 987, moveX = 319.70398 I/tag: mContentView.getLeft()=-540, mMenuView=540
ثالثًا، المشاكل الموجودة
1. إذا كنت قد أطلقت الفارمework، ستجد مشكلة واحدة:
عندما يتم سحب item واحد من ListView، لنفترض أن يكون item1؛ في هذه الحالة، يتم سحب item آخر، نسميه item2;
في هذه الحالة لن يُغلق item1، و بالطبع لن يُفتح item2.
هذا التأثير ليس جيدًا، لقد قمت بتعديل هذه المشكلة في الكود. الكود المحدد، لقد وضعت علامة عليه.
2. هذا الكود أدناه: في SwipeMenuListView onTouchEvent(MotionEvent ev) في ACTION_DOWN، هذا الكود لن يُت�行 أبدًا، لأن onTouchEvent و onInterceptTouchEvent يتعلقان بنفس MotionEvent.
mTouchPosition == oldPos دائمًا متساويتان.
//هذا الطريقة لن تُت�行 أبداً! نية المؤلف هو إغلاق القائمة عند mTouchPosition != oldPos، ولكن بناءً على هذا الكود هاتان القيمتان ستكونان دائمًا متساويتان، //بما أن هذا يتعلق بـ MotionEvent فبالطبع يبدو متطابقًا if (mTouchView != null && mTouchView.isOpen()) { //إغلاق swipeMenu mTouchView.smoothCloseMenu(); //mTouchView = null; // return super.onTouchEvent(ev); // محاولة إلغاء حدث اللمس MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); onTouchEvent(cancelEvent); // إلغاء الحدث، ينتهي الوقت // تنفيذ العودة على إغلاق menu if (mOnMenuStateChangeListener != null) { mOnMenuStateChangeListener.onMenuClose(oldPos); } return true; }
لقد قمت بتعديل هذه المشكلة في الكود. الآن تم إرسالها إلى المطور الأصلي على GitHub.
شكرًا على القراءة، نأمل أن تساعدكم، شكرًا لدعمكم لهذا الموقع!
البيان: محتويات هذا المقال تم جمعها من الإنترنت، وتحتفظ الملكية بحقوق الطبع والتأليف للأصحابها، ويتم تقديم المحتوى من قبل المستخدمين عبر الإنترنت بطرقهم الخاصة، والوقت الذي يتم فيه إدراجه على هذا الموقع لا يمتلك الحقوق، ولا يتم تعديل المحتوى بشكل يدوي، ولا يتحمل الموقع أي مسؤولية قانونية مرتبطة. إذا اكتشفت أن هناك محتوى يشتبه في حقوق الطبع والتأليف، فلا تتردد في إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (أثناء إرسال البريد الإلكتروني، يرجى استبدال '#' ب '@') لإبلاغنا، وقدم الأدلة ذات الصلة، وسيتم حذف المحتوى المشتبه فيه فور التحقق منه.