├── .gitattributes ├── .gitignore ├── 20150106071617515.gif ├── README.md ├── library ├── AndroidManifest.xml ├── ic_launcher-web.png ├── libs │ └── android-support-v4.jar ├── proguard-project.txt ├── project.properties ├── res │ └── values │ │ └── slidelistview_attrs.xml └── src │ └── com │ └── roamer │ └── slidelistview │ ├── SlideBaseAdapter.java │ ├── SlideItemListener.java │ ├── SlideListView.java │ ├── SlideTouchListener.java │ └── wrap │ ├── FrontViewWrapLayout.java │ └── SlideItemWrapLayout.java └── sample ├── AndroidManifest.xml ├── ic_launcher-web.png ├── libs └── android-support-v4.jar ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── layout │ ├── activity_main.xml │ ├── row_front_view.xml │ ├── row_left_back_view.xml │ └── row_right_back_view.xml ├── menu │ └── main.xml ├── values-v11 │ └── styles.xml ├── values-v14 │ └── styles.xml ├── values-w820dp │ └── dimens.xml └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── src └── com └── example └── slidelistviewsample ├── MainActivity.java └── SlideAdapter.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /20150106071617515.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/20150106071617515.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlideListView 2 | Use for Android ListView slide delete etc. 3 | 4 | ![](https://github.com/LonelyRoamer/SlideListView/blob/master/20150106071617515.gif) 5 | 6 | ## How To Use 7 | 8 | ### 1、add in you layout.xml 9 | 18 | 19 | ### 2、implement SlideBaseAdapter,overriad getFrontViewId(),getLeftBackViewId(),getRightBackViewId() method 20 | @Override 21 | public int getFrontViewId(int position) { 22 | return R.layout.row_front_view; 23 | } 24 | 25 | @Override 26 | public int getLeftBackViewId(int position) { 27 | return R.layout.row_left_back_view; 28 | } 29 | 30 | @Override 31 | public int getRightBackViewId(int position) { 32 | return R.layout.row_right_back_view; 33 | } 34 | 35 | [See also](http://blog.csdn.net/lonelyroamer/article/details/42439875) 36 | -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/library/ic_launcher-web.png -------------------------------------------------------------------------------- /library/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/library/libs/android-support-v4.jar -------------------------------------------------------------------------------- /library/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library.reference.1=../oldninedroid_library 16 | android.library=true 17 | -------------------------------------------------------------------------------- /library/res/values/slidelistview_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /library/src/com/roamer/slidelistview/SlideBaseAdapter.java: -------------------------------------------------------------------------------- 1 | package com.roamer.slidelistview; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.widget.BaseAdapter; 6 | 7 | import com.roamer.slidelistview.SlideListView.SlideAction; 8 | import com.roamer.slidelistview.SlideListView.SlideMode; 9 | import com.roamer.slidelistview.wrap.SlideItemWrapLayout; 10 | 11 | public abstract class SlideBaseAdapter extends BaseAdapter { 12 | protected Context mContext; 13 | 14 | private SlideMode mSlideMode;// from SlideListView 15 | private SlideAction mSlideLeftActon;// from SlideListView 16 | private SlideAction mSlideRightActon;// from SlideListView 17 | 18 | public SlideBaseAdapter(Context context) { 19 | mContext = context; 20 | mSlideMode = SlideMode.getDefault(); 21 | mSlideLeftActon = SlideAction.getDefault(); 22 | mSlideRightActon = SlideAction.getDefault(); 23 | } 24 | 25 | // package access method,you should not change the implement 26 | void setSlideMode(SlideMode slideMode) { 27 | mSlideMode = slideMode; 28 | } 29 | 30 | void setSlideLeftAction(SlideAction slideAction) { 31 | mSlideLeftActon = slideAction; 32 | } 33 | 34 | void setSlideRightAction(SlideAction slideAction) { 35 | mSlideRightActon = slideAction; 36 | } 37 | 38 | /** 39 | * At first,your whole item slide mode is base on the SlideListView's 40 | * SlideMode.
41 | * but your can change the slide mode at one or more position in this 42 | * adapter by override this method 43 | * 44 | * @param position 45 | * @return 46 | */ 47 | public SlideMode getSlideModeInPosition(int position) { 48 | return mSlideMode; 49 | } 50 | 51 | /** 52 | * Provide your front view layout id.Must be effective(cann't be 0) 53 | * 54 | * @return 55 | */ 56 | public abstract int getFrontViewId(int position); 57 | 58 | /** 59 | * Provide your left back view layout id.If you don't need left back 60 | * view,return 0 61 | * 62 | * @return 63 | */ 64 | public abstract int getLeftBackViewId(int position); 65 | 66 | /** 67 | * Provide your right back view layout id.If you don't need right back 68 | * view,return 0 69 | * 70 | * @return 71 | */ 72 | public abstract int getRightBackViewId(int position); 73 | 74 | /** 75 | * In your getView() method,when you want to create convertView,you must 76 | * call this method rather than create yourself
77 | * example:
78 | * 79 | * ViewHolder holder=null; 80 | * if(convertView==null){ 81 | * convertView=createConvertView(position); 82 | * holder=new ViewHolder(); 83 | * convertView.setTag(holder); 84 | * }else{ 85 | * holder=(ViewHolder)convertView.getTag(); 86 | * } 87 | * 88 | * 89 | * @return 90 | */ 91 | protected View createConvertView(int position) { 92 | SlideItemWrapLayout item = new SlideItemWrapLayout(mContext, mSlideLeftActon, mSlideRightActon, getFrontViewId(position), 93 | getLeftBackViewId(position), getRightBackViewId(position)); 94 | return item; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /library/src/com/roamer/slidelistview/SlideItemListener.java: -------------------------------------------------------------------------------- 1 | package com.roamer.slidelistview; 2 | 3 | /** 4 | * Listener to get callback notifications for the SlideListView 5 | */ 6 | public interface SlideItemListener { 7 | /** 8 | * 9 | * @param position 10 | * @param left 11 | * opend left back view or right back view.if true,opend left 12 | * back view,else right 13 | */ 14 | void onOpend(int position, boolean left); 15 | 16 | /** 17 | * 18 | * @param position 19 | * @param left 20 | * opend left back view or right back view.if true,opend left 21 | * back view,else right 22 | */ 23 | void onClosed(int position, boolean left); 24 | } 25 | -------------------------------------------------------------------------------- /library/src/com/roamer/slidelistview/SlideListView.java: -------------------------------------------------------------------------------- 1 | package com.roamer.slidelistview; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.database.DataSetObserver; 8 | import android.os.Build; 9 | import android.support.v4.view.MotionEventCompat; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.widget.AbsListView; 15 | import android.widget.AdapterView; 16 | import android.widget.ListAdapter; 17 | import android.widget.ListView; 18 | 19 | import com.example.swiplistview.R; 20 | 21 | public class SlideListView extends ListView { 22 | public static final boolean DEUBG = true; 23 | public static final String TAG = SlideListView.class.getSimpleName(); 24 | // extern Listener 25 | private OnItemClickListener mOnItemClickListener; 26 | private OnScrollListener mOnScrollListener; 27 | private SlideItemListener mSlideItemListener; 28 | // inner listener 29 | private SlideTouchListener mTouchListener; 30 | // Slide value 31 | private long mAnimationTime; 32 | private SlideMode mSlideMode; 33 | private SlideAction mSlideLeftAction; 34 | private SlideAction mSlideRightAction; 35 | 36 | private SlideBaseAdapter mAdapter; 37 | 38 | private boolean isInScrolling = false; 39 | 40 | private static Field sTouch_Mode_Field; 41 | static { 42 | try { 43 | sTouch_Mode_Field = AbsListView.class.getDeclaredField("mTouchMode"); 44 | sTouch_Mode_Field.setAccessible(true); 45 | } catch (NoSuchFieldException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | public SlideListView(Context context) { 51 | super(context); 52 | init(null); 53 | } 54 | 55 | public SlideListView(Context context, AttributeSet attrs) { 56 | super(context, attrs); 57 | init(attrs); 58 | } 59 | 60 | public SlideListView(Context context, AttributeSet attrs, int defStyle) { 61 | super(context, attrs, defStyle); 62 | init(attrs); 63 | } 64 | 65 | /** 66 | * Init ListView 67 | * 68 | * @param attrs 69 | * AttributeSet 70 | */ 71 | private void init(AttributeSet attrs) { 72 | if (attrs != null) { 73 | TypedArray styled = getContext().obtainStyledAttributes(attrs, R.styleable.SlideListView); 74 | mAnimationTime = styled.getInteger(R.styleable.SlideListView_slideAnimationTime, 0); 75 | mSlideMode = SlideMode.mapIntToValue(styled.getInteger(R.styleable.SlideListView_slideMode, 0)); 76 | mSlideLeftAction = SlideAction.mapIntToValue(styled.getInteger(R.styleable.SlideListView_slideLeftAction, 0)); 77 | mSlideRightAction = SlideAction.mapIntToValue(styled.getInteger(R.styleable.SlideListView_slideRightAction, 0)); 78 | styled.recycle(); 79 | } 80 | mTouchListener = new SlideTouchListener(this); 81 | // You can't use setOnTouchListener() in your own code 82 | setOnTouchListener(mTouchListener); 83 | // You can use setOnScrollListener() in your own code 84 | setOnScrollListener(mInnerOnScrollListener); 85 | // You can use setOnItemClickListener() in your own code 86 | setOnItemClickListener(mInnerOnItemClickListener); 87 | } 88 | 89 | @Override 90 | public void setOnItemClickListener(OnItemClickListener listener) { 91 | if (listener != mInnerOnItemClickListener) { 92 | mOnItemClickListener = listener; 93 | } else { 94 | super.setOnItemClickListener(listener); 95 | } 96 | } 97 | 98 | @Override 99 | public void setOnScrollListener(OnScrollListener listener) { 100 | if (listener != mInnerOnScrollListener) { 101 | mOnScrollListener = listener; 102 | } else { 103 | super.setOnScrollListener(listener); 104 | } 105 | } 106 | 107 | private OnScrollListener mInnerOnScrollListener = new OnScrollListener() { 108 | @Override 109 | public void onScrollStateChanged(AbsListView view, int scrollState) { 110 | if (scrollState == SCROLL_STATE_IDLE) { 111 | isInScrolling = false; 112 | } else { 113 | isInScrolling = true; 114 | } 115 | if (mOnScrollListener != null) { 116 | mOnScrollListener.onScrollStateChanged(view, scrollState); 117 | } 118 | } 119 | 120 | @Override 121 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 122 | if (mOnScrollListener != null) { 123 | mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); 124 | } 125 | } 126 | }; 127 | 128 | private AdapterView.OnItemClickListener mInnerOnItemClickListener = new OnItemClickListener() { 129 | 130 | @Override 131 | public void onItemClick(AdapterView parent, View view, int position, long id) { 132 | if (mTouchListener.isOpend()) { 133 | mTouchListener.closeOpenedItem(); 134 | return; 135 | } 136 | if (mOnItemClickListener != null) { 137 | mOnItemClickListener.onItemClick(parent, view, position, id); 138 | } 139 | } 140 | }; 141 | 142 | public void setSlideItemListener(SlideItemListener listener) { 143 | mSlideItemListener = listener; 144 | } 145 | 146 | // notify opend 147 | void onOpend(int position, boolean left) { 148 | if (DEUBG) { 149 | Log.d(TAG, (left ? "left" : "right") + " back view " + "is opend at position " + position); 150 | } 151 | if (mSlideItemListener != null) { 152 | mSlideItemListener.onOpend(position, left); 153 | } 154 | } 155 | 156 | // notify closed 157 | void onClosed(int position, boolean left) { 158 | if (DEUBG) { 159 | Log.d(TAG, (left ? "left" : "right") + " back view " + "is closed at position " + position); 160 | } 161 | if (mSlideItemListener != null) { 162 | mSlideItemListener.onClosed(position, left); 163 | } 164 | } 165 | 166 | boolean isInScrolling() { 167 | return isInScrolling; 168 | } 169 | 170 | boolean isSlideAdapter() { 171 | return mAdapter != null; 172 | } 173 | 174 | SlideBaseAdapter getSlideAdapter() { 175 | return mAdapter; 176 | } 177 | 178 | boolean isSlideEnable() { 179 | return isSlideAdapter() && mSlideMode != SlideMode.NONE; 180 | } 181 | 182 | public void setSlideMode(SlideMode slideMode) { 183 | if (mSlideMode != slideMode) { 184 | if (isSlideAdapter()) { 185 | if (mTouchListener.isOpend()) { 186 | mTouchListener.closeOpenedItem(); 187 | } 188 | mAdapter.setSlideMode(slideMode); 189 | mAdapter.notifyDataSetInvalidated(); 190 | } 191 | mSlideMode = slideMode; 192 | } 193 | } 194 | 195 | public SlideMode getSlideMode() { 196 | return mSlideMode; 197 | } 198 | 199 | public void setSlideLeftAction(SlideAction slideAction) { 200 | if (mSlideLeftAction != slideAction) { 201 | if (isSlideAdapter()) { 202 | if (mTouchListener.isOpend()) { 203 | mTouchListener.closeOpenedItem(); 204 | } 205 | } 206 | mSlideLeftAction = slideAction; 207 | if (isSlideAdapter()) { 208 | SlideBaseAdapter adapter = mAdapter; 209 | setAdapter(null); 210 | setAdapter(adapter); 211 | } 212 | } 213 | } 214 | 215 | public SlideAction getSlideLeftAction() { 216 | return mSlideLeftAction; 217 | } 218 | 219 | public void setSlideRightAction(SlideAction slideAction) { 220 | if (mSlideRightAction != slideAction) { 221 | if (isSlideAdapter()) { 222 | if (mTouchListener.isOpend()) { 223 | mTouchListener.closeOpenedItem(); 224 | } 225 | } 226 | mSlideRightAction = slideAction; 227 | if (isSlideAdapter()) { 228 | SlideBaseAdapter adapter = mAdapter; 229 | setAdapter(null); 230 | setAdapter(adapter); 231 | } 232 | } 233 | } 234 | 235 | public SlideAction getSlideRightAction() { 236 | return mSlideRightAction; 237 | } 238 | 239 | public long getAnimationTime() { 240 | return mAnimationTime; 241 | } 242 | 243 | public void setAnimationTime(long animationTime) { 244 | this.mAnimationTime = animationTime; 245 | } 246 | 247 | private class InnerDataSetObserver extends DataSetObserver { 248 | @Override 249 | public void onChanged() { 250 | super.onChanged(); 251 | closeDirect(); 252 | } 253 | 254 | @Override 255 | public void onInvalidated() { 256 | super.onInvalidated(); 257 | closeDirect(); 258 | } 259 | } 260 | 261 | private void closeDirect() { 262 | if (DEUBG) { 263 | Log.e(TAG, "Adapter data has changed"); 264 | } 265 | if (mTouchListener.isOpend()) { 266 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 267 | postDelayed(new Runnable() { 268 | @Override 269 | public void run() { 270 | mTouchListener.closeOpenedItem(); 271 | } 272 | }, 100); 273 | } else { 274 | mTouchListener.closeOpenedItem(); 275 | } 276 | } else { 277 | mTouchListener.reset(); 278 | } 279 | } 280 | 281 | private InnerDataSetObserver mInnerDataSetObserver; 282 | 283 | @Override 284 | public void setAdapter(ListAdapter adapter) { 285 | if (mAdapter != null && mInnerDataSetObserver != null) { 286 | mAdapter.unregisterDataSetObserver(mInnerDataSetObserver); 287 | } 288 | mAdapter = null; 289 | mInnerDataSetObserver = null; 290 | if (adapter != null && adapter instanceof SlideBaseAdapter) { 291 | mAdapter = (SlideBaseAdapter) adapter; 292 | mAdapter.setSlideMode(mSlideMode); 293 | mAdapter.setSlideLeftAction(mSlideLeftAction); 294 | mAdapter.setSlideRightAction(mSlideRightAction); 295 | mInnerDataSetObserver = new InnerDataSetObserver(); 296 | mAdapter.registerDataSetObserver(mInnerDataSetObserver); 297 | } 298 | super.setAdapter(adapter); 299 | closeDirect(); 300 | } 301 | 302 | public boolean dispatchTouchEvent(MotionEvent ev) { 303 | if (isEnabled() && isSlideEnable()) { 304 | int action = MotionEventCompat.getActionMasked(ev); 305 | if (action == MotionEvent.ACTION_DOWN) { 306 | int downPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); 307 | int opendPosition = mTouchListener.getOpendPosition(); 308 | // There is a item in opend or half opend(exception occured in 309 | // previous slideing event) status 310 | if (opendPosition != INVALID_POSITION) { 311 | // if slideing or auto 312 | // slideing(SlideTouchListener.autoScroll()) has not 313 | // finished,drop this motion event(avoid 314 | // NullPointerException) 315 | if (mTouchListener.isInSliding()) { 316 | return false; 317 | } 318 | // if down position not equals the opend position,drop this 319 | // motion event and close the opend item 320 | if (downPosition != opendPosition) { 321 | mTouchListener.closeOpenedItem(); 322 | return false; 323 | } 324 | } 325 | } 326 | } 327 | return super.dispatchTouchEvent(ev); 328 | } 329 | 330 | @Override 331 | public boolean onInterceptTouchEvent(MotionEvent ev) { 332 | if (isEnabled() && isSlideEnable()) { 333 | if (mTouchListener.onInterceptTouchEvent(ev)) { 334 | return true; 335 | } 336 | } 337 | return super.onInterceptTouchEvent(ev); 338 | } 339 | 340 | void checkScrolling() { 341 | if (!isInScrolling) { 342 | return; 343 | } 344 | if (sTouch_Mode_Field == null) { 345 | return; 346 | } 347 | int touchMode = 0; 348 | try { 349 | touchMode = sTouch_Mode_Field.getInt(this); 350 | } catch (IllegalAccessException e1) { 351 | e1.printStackTrace(); 352 | } catch (IllegalArgumentException e1) { 353 | e1.printStackTrace(); 354 | } 355 | if (DEUBG) { 356 | Log.d(TAG, "mTouchMode:" + touchMode); 357 | } 358 | if (touchMode == -1) {// touchMode==TOUCH_MODE_REST 359 | isInScrolling = false; 360 | } 361 | } 362 | 363 | public static enum SlideMode { 364 | NONE(0x0), LEFT(0x1), RIGHT(0x2), BOTH(0x3); 365 | /** 366 | * Maps an int to a specific mode. This is needed when saving state, or 367 | * inflating the view from XML where the mode is given through a attr 368 | * int. 369 | * 370 | * @param modeInt 371 | * - int to map a Mode to 372 | * @return Mode that modeInt maps to, or PULL_FROM_START by default. 373 | */ 374 | static SlideMode mapIntToValue(final int modeInt) { 375 | for (SlideMode value : SlideMode.values()) { 376 | if (modeInt == value.getIntValue()) { 377 | return value; 378 | } 379 | } 380 | // If not, return default 381 | return getDefault(); 382 | } 383 | 384 | static SlideMode getDefault() { 385 | return NONE; 386 | } 387 | 388 | private int mIntValue; 389 | 390 | // The modeInt values need to match those from attrs.xml 391 | SlideMode(int modeInt) { 392 | mIntValue = modeInt; 393 | } 394 | 395 | int getIntValue() { 396 | return mIntValue; 397 | } 398 | 399 | } 400 | 401 | public static enum SlideAction { 402 | SCROLL(0x0), REVEAL(0x1); 403 | /** 404 | * Maps an int to a specific mode. This is needed when saving state, or 405 | * inflating the view from XML where the mode is given through a attr 406 | * int. 407 | * 408 | * @param modeInt 409 | * - int to map a Mode to 410 | * @return Mode that modeInt maps to, or PULL_FROM_START by default. 411 | */ 412 | static SlideAction mapIntToValue(final int actionInt) { 413 | for (SlideAction value : SlideAction.values()) { 414 | if (actionInt == value.getIntValue()) { 415 | return value; 416 | } 417 | } 418 | 419 | // If not, return default 420 | return getDefault(); 421 | } 422 | 423 | static SlideAction getDefault() { 424 | return SCROLL; 425 | } 426 | 427 | private int mIntValue; 428 | 429 | // The modeInt values need to match those from attrs.xml 430 | SlideAction(int actionInt) { 431 | mIntValue = actionInt; 432 | } 433 | 434 | int getIntValue() { 435 | return mIntValue; 436 | } 437 | 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /library/src/com/roamer/slidelistview/SlideTouchListener.java: -------------------------------------------------------------------------------- 1 | package com.roamer.slidelistview; 2 | 3 | import static com.nineoldandroids.view.ViewHelper.setTranslationX; 4 | import static com.nineoldandroids.view.ViewPropertyAnimator.animate; 5 | import android.os.Build; 6 | import android.support.v4.view.MotionEventCompat; 7 | import android.util.Log; 8 | import android.view.MotionEvent; 9 | import android.view.VelocityTracker; 10 | import android.view.View; 11 | import android.view.View.OnTouchListener; 12 | import android.view.ViewConfiguration; 13 | import android.view.ViewParent; 14 | import android.widget.AbsListView; 15 | 16 | import com.nineoldandroids.animation.Animator; 17 | import com.nineoldandroids.animation.AnimatorListenerAdapter; 18 | import com.roamer.slidelistview.SlideListView.SlideAction; 19 | import com.roamer.slidelistview.SlideListView.SlideMode; 20 | import com.roamer.slidelistview.wrap.FrontViewWrapLayout; 21 | import com.roamer.slidelistview.wrap.SlideItemWrapLayout; 22 | 23 | public class SlideTouchListener implements OnTouchListener { 24 | private static final int INVALID_POINTER = -1; 25 | // Sliding status 26 | private static final int SLIDING_STATE_NONE = 0;// no sliding 27 | private static final int SLIDING_STATE_MANUAL = 1;// manual sliding 28 | private static final int SLIDING_STATE_AUTO = 2;// auto sliding 29 | 30 | private SlideListView mSlideListView; 31 | private int mTouchSlop; 32 | private long mConfigShortAnimationTime; 33 | 34 | private int mDownPosition; 35 | private int mActivePointerId; 36 | private int mDownMotionX; 37 | private VelocityTracker mVelocityTracker; 38 | private int mScrollState = SLIDING_STATE_NONE; 39 | // 40 | private SlideItem mSlideItem; 41 | 42 | public SlideTouchListener(SlideListView slideListView) { 43 | mSlideListView = slideListView; 44 | ViewConfiguration configuration = ViewConfiguration.get(slideListView.getContext()); 45 | mTouchSlop = configuration.getScaledTouchSlop(); 46 | mConfigShortAnimationTime = slideListView.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime); 47 | } 48 | 49 | /** 50 | * reset items status when adapter is modified 51 | */ 52 | void reset() { 53 | mSlideItem = null; 54 | mScrollState = SLIDING_STATE_NONE; 55 | } 56 | 57 | /** 58 | * If there is a item in opend status,return the position.Else return 59 | * AbsListView.INVALID_POSITION =-1 60 | * 61 | * @return 62 | */ 63 | public int getOpendPosition() { 64 | if (isOpend()) { 65 | return mSlideItem.position; 66 | } 67 | return AbsListView.INVALID_POSITION; 68 | } 69 | 70 | /** 71 | * 72 | * @return if return true,there is in sliding,else not 73 | */ 74 | boolean isInSliding() { 75 | return mScrollState != SLIDING_STATE_NONE; 76 | } 77 | 78 | /** 79 | * If there is a item in opend status,close it.Else do no thing 80 | */ 81 | public void closeOpenedItem() { 82 | if (isOpend()) { 83 | autoScroll(mSlideItem.offset, false); 84 | } 85 | } 86 | 87 | public boolean isOpend() { 88 | return mSlideItem != null && mSlideItem.isOpend(); 89 | } 90 | 91 | private long getAnimationTime() { 92 | long time = mSlideListView.getAnimationTime(); 93 | if (time <= 0) { 94 | time = mConfigShortAnimationTime; 95 | } 96 | return time; 97 | } 98 | 99 | private void initOrResetVelocityTracker() { 100 | if (mVelocityTracker == null) { 101 | mVelocityTracker = VelocityTracker.obtain(); 102 | } else { 103 | mVelocityTracker.clear(); 104 | } 105 | } 106 | 107 | private void initVelocityTrackerIfNotExists() { 108 | if (mVelocityTracker == null) { 109 | mVelocityTracker = VelocityTracker.obtain(); 110 | } 111 | } 112 | 113 | private class SlideItem { 114 | /** 115 | * The slideItem's position 116 | */ 117 | private final int position; 118 | // slide item's view 119 | private SlideItemWrapLayout child; 120 | private FrontViewWrapLayout frontView; 121 | private View leftBackView; 122 | private View rightBackView; 123 | /** 124 | * represent the offset of slide item(Actual,it is the front view's 125 | * offset).
126 | * The value must between {@link #minOffset} and {@link #maxOffset}.
127 | * if the item has no sliding any more,offset==0. 128 | */ 129 | private int offset; 130 | 131 | /** 132 | * if rightBackView!=null && rightBackView.getWidth()!=0,then the 133 | * item(front view) can sliding to left.
134 | * than the {@link #offset} will less than 0.So the minOffset= 135 | * -rightBackView.getWidth(); 136 | */ 137 | private final int minOffset; 138 | 139 | /** 140 | * if leftBackView!=null && leftBackView.getWidth()!=0,then the 141 | * item(front view) can sliding to right.
142 | * than the {@link #offset} will greater than 0.So the maxOffset= 143 | * leftBackView.getWidth(); 144 | */ 145 | private final int maxOffset; 146 | 147 | /** 148 | * Record the previous offset value.Used for notify 149 | * {@link #SlideItemListener} 150 | */ 151 | private int previousOffset; 152 | 153 | private int preDelatX; 154 | 155 | private int gingerbread_mr1_Offset;// Use fro sdk_version<=2.3.3.Else 156 | // always be 0 157 | 158 | public SlideItem(int pos) { 159 | position = pos; 160 | child = (SlideItemWrapLayout) mSlideListView.getChildAt(position - mSlideListView.getFirstVisiblePosition()); 161 | if (child == null) { 162 | throw new NullPointerException("At position:" + position 163 | + "child(Item) cann't be null.Are your sure you have use createConvertView() method in your adapter"); 164 | } 165 | frontView = child.getFrontView(); 166 | if (frontView == null) { 167 | throw new NullPointerException("At position:" + position 168 | + "front view cann't be null.Are your sure you have use createConvertView() method in your adapter"); 169 | } 170 | leftBackView = child.getLeftBackView(); 171 | rightBackView = child.getRightBackView(); 172 | SlideMode slideMode = mSlideListView.getSlideAdapter().getSlideModeInPosition(position - mSlideListView.getHeaderViewsCount()); 173 | if (rightBackView != null && (slideMode == SlideMode.RIGHT || slideMode == SlideMode.BOTH)) { 174 | minOffset = -rightBackView.getWidth(); 175 | } else { 176 | minOffset = 0; 177 | } 178 | if (leftBackView != null && (slideMode == SlideMode.LEFT || slideMode == SlideMode.BOTH)) { 179 | maxOffset = leftBackView.getWidth(); 180 | } else { 181 | maxOffset = 0; 182 | } 183 | } 184 | 185 | private boolean isOpend() { 186 | return offset != 0 /* 187 | * && (xOffset == xMinOffset || xOffset == 188 | * xMaxOffset) 189 | */; 190 | } 191 | } 192 | 193 | private int getPointerIndex(MotionEvent event) { 194 | int pointerIndex = event.findPointerIndex(mActivePointerId); 195 | if (pointerIndex == INVALID_POINTER) { 196 | pointerIndex = 0; 197 | mActivePointerId = event.getPointerId(pointerIndex); 198 | } 199 | return pointerIndex; 200 | } 201 | 202 | boolean onInterceptTouchEvent(MotionEvent event) { 203 | int action = MotionEventCompat.getActionMasked(event); 204 | switch (action) { 205 | case MotionEvent.ACTION_DOWN: {// All MotionEvent.ACTION_DOWN will 206 | // dispatch to here 207 | if (isInSliding()) {// if previous slideing has not finished,prevent 208 | // it 209 | return true; 210 | } 211 | // reset 212 | mDownPosition = AbsListView.INVALID_POSITION; 213 | mDownMotionX = 0; 214 | mActivePointerId = INVALID_POINTER; 215 | 216 | int position = mSlideListView.pointToPosition((int) event.getX(), (int) event.getY()); 217 | if (position == AbsListView.INVALID_POSITION) { 218 | break; 219 | } 220 | // don't allow swiping if this is on the header or footer or 221 | // IGNORE_ITEM_VIEW_TYPE or enabled is false on the adapter 222 | boolean allowSlide = mSlideListView.getAdapter().isEnabled(position) && mSlideListView.getAdapter().getItemViewType(position) >= 0; 223 | if (allowSlide) { 224 | // below or equals 3.0,the OnScrollListener callback has 225 | // error,so we need check the ListView scroll state 226 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) { 227 | mSlideListView.checkScrolling(); 228 | } 229 | mDownPosition = position; 230 | mActivePointerId = event.getPointerId(0); 231 | mDownMotionX = (int) event.getX(); 232 | initOrResetVelocityTracker(); 233 | mVelocityTracker.addMovement(event); 234 | } 235 | } 236 | break; 237 | case MotionEvent.ACTION_MOVE: {// If MotionEvent.ACTION_DOWN in the sub 238 | // view,MotionEvent.ACTION_MOVE will 239 | // dispatch to here at 240 | // first.So if it is sliding some 241 | // distance on the x axis,we should 242 | // intercept the touch event 243 | if (mDownPosition == AbsListView.INVALID_POSITION) { 244 | break; 245 | } 246 | if (mSlideListView.isInScrolling()) { 247 | break; 248 | } 249 | int pointerIndex = getPointerIndex(event); 250 | // get scroll speed 251 | initVelocityTrackerIfNotExists(); 252 | mVelocityTracker.addMovement(event); 253 | mVelocityTracker.computeCurrentVelocity(1000); 254 | float velocityX = Math.abs(mVelocityTracker.getXVelocity(mActivePointerId)); 255 | float velocityY = Math.abs(mVelocityTracker.getYVelocity(mActivePointerId)); 256 | // whether is scroll on x axis 257 | boolean isScrollX = velocityX > velocityY; 258 | // get scroll distance 259 | int distance = Math.abs((int) event.getX(pointerIndex) - mDownMotionX); 260 | 261 | if (isScrollX && distance > mTouchSlop) { 262 | ViewParent parent = mSlideListView.getParent(); 263 | if (parent != null) { 264 | parent.requestDisallowInterceptTouchEvent(true); 265 | } 266 | mScrollState = SLIDING_STATE_MANUAL; 267 | return true; 268 | } 269 | } 270 | break; 271 | // case MotionEvent.ACTION_UP: 272 | // case MotionEvent.ACTION_CANCEL: 273 | // default: 274 | // mScrollState = SLIDING_STATE_NONE; 275 | // break; 276 | } 277 | return false; 278 | } 279 | 280 | @Override 281 | public boolean onTouch(View v, MotionEvent event) { 282 | if (!mSlideListView.isEnabled() || !mSlideListView.isSlideEnable()) { 283 | return false; 284 | } 285 | int action = MotionEventCompat.getActionMasked(event); 286 | switch (action) { 287 | case MotionEvent.ACTION_DOWN: { 288 | if (isInSliding()) {// if previous slideing has not finished,prevent 289 | // it 290 | return true; 291 | } 292 | } 293 | break; 294 | case MotionEvent.ACTION_MOVE: {// Handle the event which dispatch to 295 | // ListView.If is in 296 | // sliding,intercept(return true) 297 | if (mDownPosition == AbsListView.INVALID_POSITION) { 298 | break; 299 | } 300 | if (mSlideListView.isInScrolling()) { 301 | break; 302 | } 303 | int pointerIndex = getPointerIndex(event); 304 | 305 | if (mScrollState == SLIDING_STATE_MANUAL) { 306 | if (mSlideItem == null) {// start sliding and init mSlideItem 307 | mSlideItem = new SlideItem(mDownPosition); 308 | } 309 | 310 | int deltaX = (int) event.getX(pointerIndex) - mDownMotionX; 311 | int nextOffset = deltaX - mSlideItem.preDelatX + mSlideItem.offset; 312 | mSlideItem.preDelatX = deltaX; 313 | if (nextOffset < mSlideItem.minOffset) { 314 | nextOffset = mSlideItem.minOffset; 315 | } 316 | if (nextOffset > mSlideItem.maxOffset) { 317 | nextOffset = mSlideItem.maxOffset; 318 | } 319 | if (mSlideItem.offset != nextOffset) { 320 | mSlideItem.offset = nextOffset; 321 | move(nextOffset); 322 | } 323 | return true; 324 | } else { 325 | // See onInterceptTouchEvent() Method 326 | initVelocityTrackerIfNotExists(); 327 | mVelocityTracker.addMovement(event); 328 | mVelocityTracker.computeCurrentVelocity(1000); 329 | float velocityX = Math.abs(mVelocityTracker.getXVelocity(mActivePointerId)); 330 | float velocityY = Math.abs(mVelocityTracker.getYVelocity(mActivePointerId)); 331 | // whether is scroll on x axis 332 | boolean isScrollX = velocityX > velocityY; 333 | // get scroll distance 334 | int distance = Math.abs((int) event.getX(pointerIndex) - mDownMotionX); 335 | 336 | if (isScrollX && distance > mTouchSlop) { 337 | ViewParent parent = mSlideListView.getParent(); 338 | if (parent != null) { 339 | parent.requestDisallowInterceptTouchEvent(true); 340 | } 341 | mScrollState = SLIDING_STATE_MANUAL; 342 | return true; 343 | } 344 | } 345 | } 346 | break; 347 | case MotionEvent.ACTION_UP: { 348 | if (mDownPosition == AbsListView.INVALID_POSITION) { 349 | break; 350 | } 351 | if (mSlideItem == null) { 352 | break; 353 | } 354 | if (mScrollState == SLIDING_STATE_MANUAL) { 355 | int pointerIndex = getPointerIndex(event); 356 | 357 | int deltaX = (int) event.getX(pointerIndex) - mDownMotionX; 358 | if (deltaX == 0) {// sliding distance equals 0 359 | reset(); 360 | return true; 361 | } 362 | /* 363 | * Don't need automatic sliding, has already reached a fixed 364 | * position 365 | */ 366 | if (mSlideItem.offset == 0 || mSlideItem.offset == mSlideItem.minOffset || mSlideItem.offset == mSlideItem.maxOffset) { 367 | slidingFinish(); 368 | return true; 369 | } 370 | 371 | SlideMode slideMode = mSlideListView.getSlideAdapter().getSlideModeInPosition( 372 | mSlideItem.position - mSlideListView.getHeaderViewsCount()); 373 | boolean doOpen = false;// open or close 374 | if (mSlideItem.offset > 0) {// left back view is showing 375 | if (slideMode == SlideMode.LEFT || slideMode == SlideMode.BOTH) {// SlideMode 376 | // support 377 | // left 378 | // the move distance greater than leftBackView's width/4 379 | boolean distanceGreater = Math.abs(mSlideItem.offset - mSlideItem.previousOffset) > Math.abs(mSlideItem.maxOffset) 380 | / (float) 4; 381 | if (mSlideItem.offset - mSlideItem.previousOffset > 0) { 382 | doOpen = distanceGreater; 383 | } else { 384 | doOpen = !distanceGreater; 385 | } 386 | } 387 | } else {// right back view is showing 388 | if (slideMode == SlideMode.RIGHT || slideMode == SlideMode.BOTH) {// SlideMode 389 | // support 390 | // right 391 | // the move distance greater than rightBackView's 392 | // width/4 393 | boolean distanceGreater = Math.abs(mSlideItem.offset - mSlideItem.previousOffset) > Math.abs(mSlideItem.minOffset) 394 | / (float) 4; 395 | if (mSlideItem.offset - mSlideItem.previousOffset > 0) { 396 | doOpen = !distanceGreater; 397 | } else { 398 | doOpen = distanceGreater; 399 | } 400 | } 401 | } 402 | autoScroll(mSlideItem.offset, doOpen); 403 | return true; 404 | } else { 405 | if (mSlideListView.isInScrolling()) { 406 | closeOpenedItem(); 407 | } 408 | } 409 | } 410 | break; 411 | case MotionEvent.ACTION_CANCEL: 412 | default: 413 | mScrollState = SLIDING_STATE_NONE; 414 | break; 415 | } 416 | return false; 417 | } 418 | 419 | private void slidingFinish() { 420 | mScrollState = SLIDING_STATE_NONE; 421 | if (mSlideItem.previousOffset != mSlideItem.offset) {// notify 422 | if (mSlideItem.previousOffset != 0) {// Previous sliding has open 423 | // left or right back 424 | // view.So wo should norify 425 | // closed 426 | // if previousOffset between 0 and maxOffset.The left back view 427 | // is opend or half opend(exception) in previous sliding 428 | boolean left = mSlideItem.previousOffset > 0 && mSlideItem.previousOffset <= mSlideItem.maxOffset; 429 | mSlideListView.onClosed(mSlideItem.position, left); 430 | } 431 | if (mSlideItem.offset != 0) {// Current sliding has open left or 432 | // right back view.So wo should 433 | // norify opend 434 | boolean left = mSlideItem.offset > 0 && mSlideItem.offset <= mSlideItem.maxOffset; 435 | mSlideListView.onOpend(mSlideItem.position, left); 436 | } 437 | 438 | // sdk_version<=2.3.3 439 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { 440 | mSlideItem.frontView.setAnimation(null); 441 | if (mSlideItem.leftBackView != null) { 442 | mSlideItem.leftBackView.setAnimation(null); 443 | } 444 | if (mSlideItem.rightBackView != null) { 445 | mSlideItem.rightBackView.setAnimation(null); 446 | } 447 | mSlideItem.child.setOffset(mSlideItem.offset); 448 | mSlideItem.gingerbread_mr1_Offset = mSlideItem.offset; 449 | } 450 | 451 | } 452 | 453 | if (mSlideItem.offset != 0) { 454 | mSlideItem.frontView.setOpend(true); 455 | mSlideItem.previousOffset = mSlideItem.offset; 456 | mSlideItem.preDelatX = 0; 457 | } else { 458 | mSlideItem.frontView.setOpend(false); 459 | mSlideItem.child.setLeftBackViewShow(false); 460 | mSlideItem.child.setRightBackViewShow(false); 461 | mSlideItem = null; 462 | } 463 | } 464 | 465 | private void autoScroll(final int offset, final boolean toOpen) { 466 | mScrollState = SLIDING_STATE_AUTO; 467 | int moveTo = 0; 468 | if (offset < 0) {// right back view is showing 469 | moveTo = toOpen ? mSlideItem.minOffset : 0; 470 | // if SlideRightAction==SCROLL,right back view will sliding with 471 | // front view 472 | SlideAction rightAction = mSlideListView.getSlideRightAction(); 473 | if (mSlideItem.rightBackView != null && rightAction == SlideAction.SCROLL) { 474 | animate(mSlideItem.rightBackView).translationX(moveTo).setDuration(getAnimationTime()); 475 | } 476 | } else {// left back view is showing 477 | moveTo = toOpen ? mSlideItem.maxOffset : 0; 478 | // if SlideLeftAction==SCROLL,left back view will sliding with front 479 | // view 480 | SlideAction leftAction = mSlideListView.getSlideLeftAction(); 481 | if (mSlideItem.leftBackView != null && leftAction == SlideAction.SCROLL) { 482 | animate(mSlideItem.leftBackView).translationX(moveTo).setDuration(getAnimationTime()); 483 | } 484 | } 485 | 486 | animate(mSlideItem.frontView).translationX(moveTo).setDuration(getAnimationTime()).setListener(new AnimatorListenerAdapter() { 487 | @Override 488 | public void onAnimationEnd(Animator animation) { 489 | // In some extreme cases,the mSlideItem will be null when the 490 | // animation end. 491 | // For example,when the item is in sliding or auto sliding,you 492 | // set a new Adapter to the listview.etc. 493 | // So,add this judgment to avoid NullPointerException 494 | if (mSlideItem == null) { 495 | if (SlideListView.DEUBG) { 496 | Log.d(SlideListView.TAG, "NullPointerException(onAnimationEnd,mSlideItem has been reset)"); 497 | } 498 | return; 499 | } 500 | if (toOpen) {// to open 501 | if (offset < 0) {// right back view is opend 502 | mSlideItem.offset = mSlideItem.minOffset; 503 | } else {// left back view is opend 504 | mSlideItem.offset = mSlideItem.maxOffset; 505 | } 506 | } else {// to close 507 | mSlideItem.offset = 0; 508 | } 509 | slidingFinish(); 510 | } 511 | }); 512 | 513 | } 514 | 515 | private void move(int offset) { 516 | setTranslationX(mSlideItem.frontView, offset - mSlideItem.gingerbread_mr1_Offset); 517 | if (offset < 0) {// offset less than 0,right back view is showing and 518 | // left dismiss 519 | if (mSlideItem.rightBackView != null) { 520 | mSlideItem.child.setRightBackViewShow(true); 521 | SlideAction rightAction = mSlideListView.getSlideRightAction(); 522 | if (rightAction == SlideAction.SCROLL) { 523 | setTranslationX(mSlideItem.rightBackView, offset - mSlideItem.gingerbread_mr1_Offset); 524 | } 525 | } 526 | if (mSlideItem.leftBackView != null) { 527 | mSlideItem.child.setLeftBackViewShow(false); 528 | } 529 | } else {// offset greater than 0,left back view is showing and right 530 | // dismiss 531 | if (mSlideItem.leftBackView != null) { 532 | mSlideItem.child.setLeftBackViewShow(true); 533 | SlideAction leftAction = mSlideListView.getSlideLeftAction(); 534 | if (leftAction == SlideAction.SCROLL) { 535 | setTranslationX(mSlideItem.leftBackView, offset - mSlideItem.gingerbread_mr1_Offset); 536 | } 537 | } 538 | if (mSlideItem.rightBackView != null) { 539 | mSlideItem.child.setRightBackViewShow(false); 540 | } 541 | } 542 | } 543 | 544 | } 545 | -------------------------------------------------------------------------------- /library/src/com/roamer/slidelistview/wrap/FrontViewWrapLayout.java: -------------------------------------------------------------------------------- 1 | package com.roamer.slidelistview.wrap; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.widget.LinearLayout; 7 | 8 | /** 9 | * wrap the front view ,so we can handle motion event more simple 10 | * 11 | * @author Dean Tao 12 | * 13 | */ 14 | public class FrontViewWrapLayout extends LinearLayout { 15 | private boolean isOpend;// whether the front view is opend 16 | 17 | public FrontViewWrapLayout(Context context) { 18 | super(context); 19 | } 20 | 21 | public FrontViewWrapLayout(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public FrontViewWrapLayout(Context context, AttributeSet attrs, int defStyle) { 26 | super(context, attrs, defStyle); 27 | } 28 | 29 | @Override 30 | public boolean dispatchTouchEvent(MotionEvent ev) { 31 | // if the front view is opend,drop all motion event(include sub view) 32 | if (isOpend) { 33 | return false; 34 | } 35 | return super.dispatchTouchEvent(ev); 36 | } 37 | 38 | public void setOpend(boolean isOpend) { 39 | this.isOpend = isOpend; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /library/src/com/roamer/slidelistview/wrap/SlideItemWrapLayout.java: -------------------------------------------------------------------------------- 1 | package com.roamer.slidelistview.wrap; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.RelativeLayout; 9 | 10 | import com.example.swiplistview.R; 11 | import com.roamer.slidelistview.SlideListView.SlideAction; 12 | 13 | /** 14 | * wrap the listview item,It may have three directly sub view(front view ,left 15 | * back view,right back view).
16 | * front view must be not null.The other is not necessarily 17 | * 18 | * @author Dean Tao 19 | * 20 | */ 21 | public class SlideItemWrapLayout extends RelativeLayout { 22 | 23 | private View mLeftBackView; 24 | private View mRightBackView; 25 | private FrontViewWrapLayout mFrontView; 26 | 27 | private SlideAction mSlideLeftAction; 28 | private SlideAction mSlideRightAction; 29 | 30 | private int mOffset = 0;// Use for sdk_version<=2.3.3 31 | 32 | /** 33 | * 34 | * @param context 35 | * @param slideLeftAction 36 | * Decided where the left view placed.
37 | * if slideLeftAction==SCROLL,will placed to left of front view
38 | * if slideLeftAction==REVEAL,will placed below of front view
39 | * @param slideRightAction 40 | * Decided where the right view placed.
41 | * if slideRightAction==SCROLL,will placed to right of front view
42 | * if slideRightAction==REVEAL,will placed below of front view
43 | * @param frontViewId 44 | * front view layout id. Must be an effective 45 | * @param leftBackViewId 46 | * left back view layout id.if leftBackViewId ==0,there is no 47 | * left back view,and the slideLeftAction will be ignored 48 | * @param rightBackViewId 49 | * right back view layout id.if rightBackViewId ==0,there is no 50 | * right back view,and the slideRightAction will be ignored 51 | */ 52 | public SlideItemWrapLayout(Context context, SlideAction slideLeftAction, SlideAction slideRightAction, int frontViewId, int leftBackViewId, 53 | int rightBackViewId) { 54 | super(context); 55 | mSlideLeftAction = slideLeftAction; 56 | mSlideRightAction = slideRightAction; 57 | init(frontViewId, leftBackViewId, rightBackViewId); 58 | } 59 | 60 | private void init(int frontViewId, int leftBackViewId, int rightBackViewId) { 61 | setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 62 | View frontView = null; 63 | if (frontViewId != 0) { 64 | frontView = LayoutInflater.from(getContext()).inflate(frontViewId, this, false); 65 | } 66 | if (frontView == null) { 67 | throw new NullPointerException("frontView can not be null"); 68 | } 69 | View leftBackView = null; 70 | View rightBackView = null; 71 | if (leftBackViewId != 0) { 72 | leftBackView = LayoutInflater.from(getContext()).inflate(leftBackViewId, this, false); 73 | } 74 | 75 | if (rightBackViewId != 0) { 76 | rightBackView = LayoutInflater.from(getContext()).inflate(rightBackViewId, this, false); 77 | } 78 | 79 | addLeftBackView(leftBackView); 80 | addRightBackView(rightBackView); 81 | addFrontView(frontView); 82 | } 83 | 84 | private void addFrontView(View frontView) { 85 | RelativeLayout.LayoutParams params = (LayoutParams) frontView.getLayoutParams(); 86 | if (params == null) { 87 | params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 88 | params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 89 | } 90 | 91 | FrontViewWrapLayout wrapLayout = new FrontViewWrapLayout(getContext()); 92 | wrapLayout.addView(frontView, params); 93 | wrapLayout.setId(R.id.slide_id_front_view); 94 | 95 | addView(wrapLayout, params); 96 | mFrontView = wrapLayout; 97 | } 98 | 99 | private void addLeftBackView(View leftBackView) { 100 | if (leftBackView == null) { 101 | return; 102 | } 103 | RelativeLayout.LayoutParams params = (LayoutParams) leftBackView.getLayoutParams(); 104 | if (params == null) {// default LayoutParams 105 | params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.MATCH_PARENT); 106 | } 107 | switch (mSlideLeftAction) { 108 | case SCROLL: 109 | params.addRule(RelativeLayout.LEFT_OF, R.id.slide_id_front_view); 110 | break; 111 | case REVEAL: 112 | params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); 113 | break; 114 | default: 115 | break; 116 | } 117 | leftBackView.setLayoutParams(params); 118 | leftBackView.setId(R.id.slide_id_left_back_view); 119 | addView(leftBackView); 120 | mLeftBackView = leftBackView; 121 | /** 122 | * must set INVISIBLE.
123 | * When the slide item is not opend,The motion event could not be 124 | * dispatch to left/right back view.
125 | * So set left/right back view INVISIBLE,then we can response the 126 | * OnItemClickListener.
127 | * (Should not be GONE,because if it is GONE,the measure width and 128 | * height will be 0) 129 | */ 130 | setLeftBackViewShow(false); 131 | } 132 | 133 | private void addRightBackView(View rightBackView) { 134 | if (rightBackView == null) { 135 | return; 136 | } 137 | RelativeLayout.LayoutParams params = (LayoutParams) rightBackView.getLayoutParams(); 138 | if (params == null) {// default LayoutParams 139 | params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.MATCH_PARENT); 140 | } 141 | switch (mSlideRightAction) { 142 | case SCROLL: 143 | params.addRule(RelativeLayout.RIGHT_OF, R.id.slide_id_front_view); 144 | break; 145 | case REVEAL: 146 | params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); 147 | break; 148 | default: 149 | break; 150 | } 151 | rightBackView.setLayoutParams(params); 152 | rightBackView.setId(R.id.slide_id_right_back_view); 153 | addView(rightBackView); 154 | mRightBackView = rightBackView; 155 | /** 156 | * must set INVISIBLE.
157 | * When the slide item is not opend,The motion event could not be 158 | * dispatch to left/right back view.
159 | * So set left/right back view INVISIBLE,then we can response the 160 | * OnItemClickListener.
161 | * (Should not be GONE,because if it is GONE,the measure width and 162 | * height will be 0) 163 | */ 164 | setRightBackViewShow(false); 165 | } 166 | 167 | @Override 168 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 169 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 170 | int parentWidthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); 171 | int parentHeightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); 172 | if (mLeftBackView != null) { 173 | LayoutParams params = (LayoutParams) mLeftBackView.getLayoutParams(); 174 | int widthSpec = ViewGroup.getChildMeasureSpec(parentWidthSpec, getPaddingLeft() + getPaddingRight() + params.leftMargin 175 | + params.rightMargin, params.width); 176 | int heightSpec = ViewGroup.getChildMeasureSpec(parentHeightSpec, getPaddingTop() + getPaddingBottom() + params.topMargin 177 | + params.bottomMargin, params.height); 178 | mLeftBackView.measure(widthSpec, heightSpec); 179 | } 180 | if (mRightBackView != null) { 181 | LayoutParams params = (LayoutParams) mRightBackView.getLayoutParams(); 182 | int widthSpec = ViewGroup.getChildMeasureSpec(parentWidthSpec, getPaddingLeft() + getPaddingRight() + params.leftMargin 183 | + params.rightMargin, params.width); 184 | int heightSpec = ViewGroup.getChildMeasureSpec(parentHeightSpec, getPaddingTop() + getPaddingBottom() + params.topMargin 185 | + params.bottomMargin, params.height); 186 | mRightBackView.measure(widthSpec, heightSpec); 187 | } 188 | } 189 | 190 | @Override 191 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 192 | super.onLayout(changed, l, t, r, b); 193 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { 194 | mFrontView.layout(mFrontView.getLeft() + mOffset, mFrontView.getTop(), mFrontView.getRight() + mOffset, mFrontView.getBottom()); 195 | } 196 | if (mLeftBackView != null) { 197 | // Always let backView in center of the item 198 | int top = (b - t - mLeftBackView.getMeasuredHeight()) / 2; 199 | if (mSlideLeftAction == SlideAction.SCROLL) { 200 | mLeftBackView.layout(mFrontView.getLeft() - mLeftBackView.getMeasuredWidth(), top, mFrontView.getLeft(), 201 | top + mLeftBackView.getMeasuredHeight()); 202 | } else { 203 | mLeftBackView.layout(mLeftBackView.getLeft(), top, mLeftBackView.getRight(), top + mLeftBackView.getMeasuredHeight()); 204 | } 205 | } 206 | if (mRightBackView != null) { 207 | // Always let backView in center of the item 208 | int top = (b - t - mRightBackView.getMeasuredHeight()) / 2; 209 | if (mSlideRightAction == SlideAction.SCROLL) { 210 | mRightBackView.layout(mFrontView.getRight(), top, mFrontView.getRight() + mRightBackView.getMeasuredWidth(), 211 | top + mRightBackView.getMeasuredHeight()); 212 | } else { 213 | mRightBackView.layout(mRightBackView.getLeft(), top, mRightBackView.getRight(), top + mRightBackView.getMeasuredHeight()); 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * front view must not be null 220 | * 221 | * @return 222 | */ 223 | public FrontViewWrapLayout getFrontView() { 224 | return mFrontView; 225 | } 226 | 227 | /** 228 | * left back view could be null 229 | * 230 | * @return 231 | */ 232 | public View getLeftBackView() { 233 | return mLeftBackView; 234 | } 235 | 236 | /** 237 | * right back view could be null 238 | * 239 | * @return 240 | */ 241 | public View getRightBackView() { 242 | return mRightBackView; 243 | } 244 | 245 | public void setLeftBackViewShow(boolean show) { 246 | setViewShow(mLeftBackView, show); 247 | } 248 | 249 | public void setRightBackViewShow(boolean show) { 250 | setViewShow(mRightBackView, show); 251 | } 252 | 253 | private void setViewShow(View view, boolean show) { 254 | if (view == null) { 255 | return; 256 | } 257 | if (show) { 258 | if (view.getVisibility() != View.VISIBLE) { 259 | view.setVisibility(View.VISIBLE); 260 | } 261 | } else { 262 | if (view.getVisibility() != View.INVISIBLE) { 263 | view.setVisibility(View.INVISIBLE); 264 | } 265 | } 266 | } 267 | 268 | public void setOffset(int offset) { 269 | if (mOffset == offset) { 270 | return; 271 | } 272 | mOffset = offset; 273 | requestLayout(); 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sample/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/sample/ic_launcher-web.png -------------------------------------------------------------------------------- /sample/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/sample/libs/android-support-v4.jar -------------------------------------------------------------------------------- /sample/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /sample/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-14 15 | android.library.reference.1=..\\slidelistview_library 16 | -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/sample/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/sample/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/sample/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilnn/SlideListView/fcbe4f4778695b74327db0ef23c90fb8fc4319d4/sample/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 |