QQ-like slide-deletion of ListView -- 2015 first blog, listview2015

Source: Internet
Author: User

QQ-like slide-deletion of ListView -- 2015 first blog, listview2015

I have always felt that the QQ contact's slide deletion function was very high recently. After several twists and turns, I finally realized this function in the New Year. It was a waste of effort to implement this function. I had the idea several times and was excited to write code implementation. As a result, the Code slapped myself, in the end, we achieved this effect using the margin Method. Okay, let's get the effect first!




After reading the results, let's look at the following ideas:


1. I used magin to implement the sliding effect between the left and right sides of the item.


2. Although the width of the text TextView is set to match_parent during item layout, the value is set to a fixed value: the screen width when you click Next.


3. Provide a method to handle conflicts between sliding and external itemClick.


Main Code:


Public class QQListView extends ListView {private int mScreenWidth; // screen width private int mDownX; // the value of private int mDownY in the pressed point x; // the value of private int mDeleteBtnWidth in the pressed point y; // The width of the delete button is private boolean isDeleteShown; // whether the delete button displays private ViewGroup mPointChild; // The currently processed itemprivate LinearLayout. layoutParams mLayoutParams; // LayoutParamspublic QQListView (Context context, AttributeSet attrs) {this (context, attrs, 0);} public QQListView (Context context, AttributeSet attrs, int defStyle) {super (context, attrs, defStyle); // obtain the screen width WindowManager wm = (WindowManager) context. getSystemService (Context. WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics (); wm. getdefadisplay display (). getMetrics (dm); mScreenWidth = dm. widthPixels ;}@ Overridepublic boolean onTouchEvent (MotionEvent ev) {switch (ev. getAction () {case Motio NEvent. ACTION_DOWN: Running mactiondown (ev); break; case MotionEvent. ACTION_MOVE: return required mactionmove (ev); case MotionEvent. ACTION_UP: Required mactionup (); break;} return super. onTouchEvent (ev);} // process action_down event private void extends mactiondown (MotionEvent ev) {if (isDeleteShown) {turnToNormal ();} mDownX = (int) ev. getX (); mDownY = (int) ev. getY (); // get the itemmPointChild = (ViewGroup) getChildAt (pointToPosi Tion (mDownX, mDownY)-getFirstVisiblePosition (); // get the width of the delete button mDeleteBtnWidth = mPointChild. getChildAt (1 ). getLayoutParams (). width; mLayoutParams = (LinearLayout. layoutParams) mPointChild. getChildAt (0 ). getLayoutParams (); // why do I need to reset layout_width to be equal to the screen width? // no matter how smooth the match_parent is, the delete button is not displayed. // why? Because when match_parent is used, ViewGroup does not layout the remaining viewmLayoutParams. width = mScreenWidth; mPointChild. getChildAt (0 ). setLayoutParams (mLayoutParams);} // process the action_move event private boolean merge mactionmove (MotionEvent ev) {int nowX = (int) ev. getX (); int nowY = (int) ev. getY (); if (Math. abs (nowX-mDownX)> Math. abs (nowY-mDownY) {// if you slide left if (nowX <mDownX) {// calculate the distance from int scroll = (nowX-mDownX)/2; // if it is greater than the width of the delete button, the maximum value is If (-scroll> = mDeleteBtnWidth) {scroll =-mDeleteBtnWidth;} // reset leftMarginmLayoutParams. leftMargin = scroll; mPointChild. getChildAt (0 ). setLayoutParams (mLayoutParams);} return true;} return super. onTouchEvent (ev);} // process the action_up event private void merge mactionup () {// if the offset is greater than half of the button, a button is displayed. // otherwise, the default if (-mLayoutParams) is restored. leftMargin> = mDeleteBtnWidth/2) {mLayoutParams. leftMargin =-mDeleteBtnWidt H; isDeleteShown = true;} else {turnToNormal ();} mPointChild. getChildAt (0 ). setLayoutParams (mLayoutParams);}/*** changed to normal */public void turnToNormal () {mLayoutParams. leftMargin = 0; mPointChild. getChildAt (0 ). setLayoutParams (mLayoutParams); isDeleteShown = false;}/*** whether to click * @ return or not */public boolean canClick () {return! IsDeleteShown ;}}


Obviously, you must choose to override ListView to achieve this effect, and rewrite onTouchEvent to determine whether to move to achieve the slide effect.


Let's take a look at the constructor method:


Public QQListView (Context context, AttributeSet attrs, int defStyle) {super (context, attrs, defStyle); // obtain the screen width WindowManager wm = (WindowManager) context. getSystemService (Context. WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics (); wm. getdefadisplay display (). getMetrics (dm); mScreenWidth = dm. widthPixels ;}


In the constructor, one thing is done: Get the screen width. Why is it necessary to get the screen width? As mentioned above, here we need to change the layout_width of the first TextView in the item to a fixed value. Isn't match_parent good? Why do I need to reset the screen width multiple times? The answer is: when the layout_width of the current View is match_parent, The ViewGroup ignores the remaining View, that is, the deleted button is not drawn!


In the onTouchEvent below, three methods are called in three cases: ACTION_DOWN, ACTION_MOVE, and ACTION_UP to process these three events.


First, let's look at what we did when we went DOWN.

// Process the action_down event private void merge mactiondown (MotionEvent ev) {if (isDeleteShown) {turnToNormal ();} mDownX = (int) ev. getX (); mDownY = (int) ev. getY (); // get the item mPointChild = (ViewGroup) getChildAt (pointToPosition (mDownX, mDownY)-getFirstVisiblePosition () of the current vertex ()); // obtain the width of the delete button. mDeleteBtnWidth = mPointChild. getChildAt (1 ). getLayoutParams (). width; mLayoutParams = (LinearLayout. layoutParams) mPoi NtChild. getChildAt (0 ). getLayoutParams (); // why do I need to reset layout_width to be equal to the screen width? // no matter how smooth the match_parent is, the delete button is not displayed. // why? Because when match_parent is used, ViewGroup does not layout the remaining view mLayoutParams. width = mScreenWidth; mPointChild. getChildAt (0). setLayoutParams (mLayoutParams );}


3 ~ Line 5: If the deleteButton of an item is displayed, the turnToNormal method is called to restore the site. The turnToNormal method is actually very simple.


7 ~ The task in line 11 is to determine the item on which the currently pressed vertex is located and obtain this item. Note that the pointToPosition method obtains the number of items in all items, while getChildAt () obtains the number of items visible to the first item. Therefore, getFirstVisiblePosition () must be subtracted () to get the correct item.


Row 13 gets the deleteButton width, which is used below to determine whether deleteButton should be fully displayed or hidden.


The most important 14 ~ In line 25, reset the layout_width of the first TextView. As for why, we have already said above.


It is a little effort to handle the move event.


// Process the action_move event private boolean merge mactionmove (MotionEvent ev) {int nowX = (int) ev. getX (); int nowY = (int) ev. getY (); if (Math. abs (nowX-mDownX)> Math. abs (nowY-mDownY) {// if you slide left if (nowX <mDownX) {// calculate the distance from int scroll = (nowX-mDownX)/2; // if it is greater than the width of the delete button, the maximum value is the width of the delete button. if (-scroll> = mDeleteBtnWidth) {scroll =-mDeleteBtnWidth;} // reset leftMargin mLayoutParams. leftMargin = scroll; mPointChild. getChildAt (0 ). setLayoutParams (mLayoutParams);} return true;} return super. onTouchEvent (ev );}


When mactionmove has a returned value, and we return its return value in onTouchEvent, the returned value is used to consume the event in our onTouchEvent while sliding horizontally, when the portrait screen slides, the event is handled by the ListView onTouchEvent to avoid blocking the ListView's up/down sliding mechanism.


3 ~ In four rows, obtain the point where the current finger is located.


Then, 5th rows are used to determine whether the displacement in the X axis is greater than that in the Y axis. If the displacement is not greater than that in the Y axis, the move event is directly handled by super.


Let's take a look at the interior of the if, followed by an if. Here, we mainly judge whether it is sliding to the left. if it slides to the left, we will calculate the expected offset in line 9th, here we take half of the finger offset.


10 ~ The 13 rows are mainly used to prevent sliding through the border and the right side is blank.


15 ~ In the 16 rows, the leftMargin value of the first TextView is changed to move to the left.


Processing up is much easier.


// Process the action_up event private void merge mactionup () {// if the offset is greater than half of the button, the button is displayed. // otherwise, the default if (-mLayoutParams) is restored. leftMargin> = mDeleteBtnWidth/2) {mLayoutParams. leftMargin =-mDeleteBtnWidth; isDeleteShown = true;} else {turnToNormal ();} mPointChild. getChildAt (0 ). setLayoutParams (mLayoutParams );}


It mainly determines whether deletebutton is to enter the display status or not by determining the leftMargin of the view.


The turnToNormal method is used in many places. Let's take a look at this custom method.

public void turnToNormal() {      mLayoutParams.leftMargin = 0;      mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);      isDeleteShown = false;  } 


This custom method is public, not because I didn't pay attention to the Code encapsulation, but it will be used externally. turnToNormal is easy to do, that is, "restore the site ".


Also, a simple custom method is used to determine whether itemClick is currently in the item clickable state when itemClick is required externally.

public boolean canClick() {      return !isDeleteShown;  }  

Well, the next step is the application. Let's look at two layout files, one is the main layout and the other is the ListView's item layout.


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context=".MainActivity" >            <org.loader.qqlist.QQListView          android:id="@+id/list"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:cacheColorHint="@android:color/transparent"          android:listSelector="@android:color/transparent"          android:divider="@android:color/darker_gray"          android:dividerHeight="2dp" />    </RelativeLayout>  


There is nothing to say, that is, the custom ListView is referenced.


Let's take a look at the item layout.

<LinearLayout xmlns: android = "http://schemas.android.com/apk/res/android" android: layout_width = "match_parent" android: layout_height = "wrap_content" android: orientation = "horizontal"> <TextView android: id = "@ + id/TV" android: layout_width = "match_parent" android: layout_height = "wrap_content" android: paddingBottom = "20dp" android: paddingLeft = "10dp" android: paddingTop = "20dp" android: background = "@ android: color/white"/> <TextView android: id = "@ + id/delete" android: layout_width = "80dp" android: layout_height = "match_parent" android: background = "# FFFF0000" android: gravity = "center" android: paddingLeft = "20dp" android: textColor = "@ android: color/white" android: paddingRight = "20dp" android: text = "delete"/> </LinearLayout>


Ah? Isn't it that the layout_width of the first TextView must be dynamically set to a fixed value: the screen width? This should be wrap_content, right? The answer is no! In retrospect, setting a fixed value is a task that can be performed only when the event of the item is handled. What about the items you have never clicked? Match_parent is required.


Finally, let's take a look at Activity.

public class MainActivity extends Activity {      private QQListView mListView;      private ArrayList<String> mData = new ArrayList<String>() {          {              for(int i=0;i<50;i++) {                  add("hello world, hello android  " + i);              }          }      };        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);                    mListView = (QQListView) findViewById(R.id.list);          mListView.setAdapter(new MyAdapter());          mListView.setOnItemClickListener(new OnItemClickListener() {              @Override              public void onItemClick(AdapterView<?> parent, View view,                      int position, long id) {                  if(mListView.canClick()) {                      Toast.makeText(MainActivity.this, mData.get(position), Toast.LENGTH_SHORT).show();                  }              }          });      }            class MyAdapter extends BaseAdapter {            @Override          public int getCount() {              return mData.size();          }            @Override          public Object getItem(int position) {              return mData.get(position);          }            @Override          public long getItemId(int position) {              return position;          }            @Override          public View getView(int position, View convertView, ViewGroup parent) {              if(null == convertView) {                  convertView = View.inflate(MainActivity.this, R.layout.item, null);              }              TextView tv = (TextView) convertView.findViewById(R.id.tv);              TextView delete = (TextView) convertView.findViewById(R.id.delete);                            tv.setText(mData.get(position));                            final int pos = position;              delete.setOnClickListener(new OnClickListener() {                  @Override                  public void onClick(View v) {                      mData.remove(pos);                      notifyDataSetChanged();                      mListView.turnToNormal();                  }              });                            return convertView;          }      }  }  


Row 62: the custom method turnToNormal is called in the onClick event of deleteButton. This ensures that after deletion, deleteButton will not continue to exist on the next item.


22 ~ The 24 rows show an additional judgment. The canClick method is used to determine whether the current item can be clicked.


Finally is the source code: http://git.oschina.net/qibin/horizontalScrollListView

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.