├── .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 | 
5 |
6 | ## How To Use
7 |
8 | ### 1、add in you layout.xml
9 |
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 |