301319e3769abf41d2b8L16">16 18
     @POST("session_end")
19
+    @FormUrlEncoded
17 20
     Call<String> endSession(@Field("session") String sessionId, @Field("lensman") String lensmanId);
18 21
 
19 22
     @POST("fetch_thumbnail")
23
+    @FormUrlEncoded
20 24
     Call<List<PhotoBean>> fetchSessionThumbnails(@Field("session") String sessionId, @Field("lensman") String lensmanId);
21 25
 
22 26
     @POST("fetch_origin")
27
+    @FormUrlEncoded
23 28
     Call<String> fetchSessionOriginPhoto(@Field("session") String sessionId, @Field("lensman") String lensmanId, @Field("name") String name);
24 29
 
25 30
 

+ 2 - 2
app/src/main/java/ai/pai/lensman/briefs/BriefsActivity.java

@@ -3,14 +3,14 @@ package ai.pai.lensman.briefs;
3 3
 import android.content.Intent;
4 4
 import android.os.Bundle;
5 5
 import android.support.annotation.Nullable;
6
-import android.support.v7.app.AppCompatActivity;
7 6
 
8 7
 import ai.pai.lensman.R;
8
+import ai.pai.lensman.base.BaseActivity;
9 9
 import ai.pai.lensman.settings.SettingsActivity;
10 10
 import butterknife.ButterKnife;
11 11
 import butterknife.OnClick;
12 12
 
13
-public class BriefsActivity extends AppCompatActivity{
13
+public class BriefsActivity extends BaseActivity{
14 14
 
15 15
     @Override
16 16
     protected void onCreate(@Nullable Bundle savedInstanceState) {

+ 29 - 0
app/src/main/java/ai/pai/lensman/login/LoginActivity.java

@@ -0,0 +1,29 @@
1
+package ai.pai.lensman.login;
2
+
3
+import android.content.Intent;
4
+import android.os.Bundle;
5
+
6
+import ai.pai.lensman.R;
7
+import ai.pai.lensman.base.BaseActivity;
8
+import ai.pai.lensman.db.Preferences;
9
+import ai.pai.lensman.upload.UploadActivity;
10
+import butterknife.ButterKnife;
11
+import butterknife.OnClick;
12
+
13
+public class LoginActivity extends BaseActivity {
14
+
15
+    @Override
16
+    protected void onCreate(Bundle savedInstanceState) {
17
+        super.onCreate(savedInstanceState);
18
+        setContentView(R.layout.activity_login);
19
+        ButterKnife.bind(this);
20
+    }
21
+
22
+    @OnClick(R.id.btn_login)
23
+    public void login(){
24
+        Preferences.getInstance(this).setLensManId("hafihafhHDFA");
25
+        startActivity(new Intent(this, UploadActivity.class));
26
+        finish();
27
+    }
28
+
29
+}

+ 2 - 2
app/src/main/java/ai/pai/lensman/qrcode/QRCaptureActivity.java

@@ -24,7 +24,6 @@ import android.graphics.BitmapFactory;
24 24
 import android.graphics.Rect;
25 25
 import android.os.Bundle;
26 26
 import android.os.Handler;
27
-import android.support.v4.app.FragmentActivity;
28 27
 import android.text.TextUtils;
29 28
 import android.util.Log;
30 29
 import android.view.SurfaceHolder;
@@ -51,6 +50,7 @@ import java.lang.reflect.Field;
51 50
 import java.util.Hashtable;
52 51
 
53 52
 import ai.pai.lensman.R;
53
+import ai.pai.lensman.base.BaseActivity;
54 54
 import ai.pai.lensman.utils.SystemUtils;
55 55
 import butterknife.BindView;
56 56
 import butterknife.ButterKnife;
@@ -65,7 +65,7 @@ import butterknife.ButterKnife;
65 65
  * @author dswitkin@google.com (Daniel Switkin)
66 66
  * @author Sean Owen
67 67
  */
68
-public class QRCaptureActivity extends FragmentActivity implements SurfaceHolder.Callback,
68
+public class QRCaptureActivity extends BaseActivity implements SurfaceHolder.Callback,
69 69
         View.OnClickListener {
70 70
 
71 71
     private static final String TAG = "QRCaptureActivity";

+ 1 - 1
app/src/main/java/ai/pai/lensman/uploadservice/Constants.java

@@ -1,4 +1,4 @@
1
-package ai.pai.lensman.uploadservice;
1
+package ai.pai.lensman.service;
2 2
 
3 3
 import android.os.Environment;
4 4
 

+ 1 - 1
app/src/main/java/ai/pai/lensman/uploadservice/ImageUtils.java

@@ -1,4 +1,4 @@
1
-package ai.pai.lensman.uploadservice;
1
+package ai.pai.lensman.service;
2 2
 
3 3
 import android.graphics.Bitmap;
4 4
 import android.graphics.BitmapFactory;

+ 1 - 1
app/src/main/java/ai/pai/lensman/uploadservice/PhotoUploadUtils.java

@@ -1,4 +1,4 @@
1
-package ai.pai.lensman.uploadservice;
1
+package ai.pai.lensman.service;
2 2
 
3 3
 import android.graphics.Bitmap;
4 4
 import android.graphics.BitmapFactory;

+ 128 - 0
app/src/main/java/ai/pai/lensman/service/UpgradeService.java

@@ -0,0 +1,128 @@
1
+package ai.pai.lensman.service;
2
+
3
+
4
+import android.app.IntentService;
5
+import android.content.Intent;
6
+import android.content.pm.PackageInfo;
7
+import android.net.Uri;
8
+import android.os.Handler;
9
+import android.os.Looper;
10
+import android.widget.Toast;
11
+
12
+import com.android.common.http.HttpUtils;
13
+import com.android.common.utils.JSONParseUtils;
14
+import com.android.common.utils.LogHelper;
15
+
16
+import org.json.JSONObject;
17
+
18
+import java.io.File;
19
+import java.io.FileOutputStream;
20
+import java.io.InputStream;
21
+import java.net.HttpURLConnection;
22
+import java.net.URL;
23
+
24
+import ai.pai.lensman.R;
25
+import ai.pai.lensman.utils.UrlContainer;
26
+
27
+
28
+public class UpgradeService extends IntentService {
29
+
30
+    private static final String TAG = "UpgradeService";
31
+    private Handler handler;
32
+
33
+    public UpgradeService() {
34
+        super(TAG);
35
+        handler = new Handler(Looper.getMainLooper());
36
+    }
37
+
38
+    @Override
39
+    protected void onHandleIntent(Intent intent) {
40
+        boolean isMuteUpdate = intent.getBooleanExtra("isMuteUpdate",true);
41
+        try{
42
+            String result = HttpUtils.doHttpPost(UrlContainer.CHECK_UPDATE_URL,null);
43
+            LogHelper.d(TAG,"检查更新结果"+result);
44
+            JSONObject json = new JSONObject(result);
45
+            int status = json.getInt("status");
46
+            if(status!=200){
47
+                LogHelper.d(TAG,"检查更新失败");
48
+                if(!isMuteUpdate){
49
+                    handler.post(new Runnable() {
50
+                        @Override
51
+                        public void run() {
52
+                            Toast.makeText(getApplicationContext(), R.string.check_version_fail,Toast.LENGTH_SHORT).show();
53
+                        }
54
+                    });
55
+                }
56
+                return;
57
+            }
58
+            JSONObject data = json.getJSONObject("data");
59
+            JSONObject appInfo = data.getJSONObject("appinfo");
60
+            String latestVersionCode = JSONParseUtils.getJSONString(appInfo.getString("latest_version_code"));
61
+            int versionCode = Integer.parseInt(latestVersionCode);
62
+            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),0);
63
+            if(versionCode<=packageInfo.versionCode){
64
+                LogHelper.d(TAG,"不需要更新");
65
+                if(!isMuteUpdate){
66
+                    handler.post(new Runnable() {
67
+                        @Override
68
+                        public void run() {
69
+                            Toast.makeText(getApplicationContext(), R.string.current_version_is_latest,Toast.LENGTH_SHORT).show();
70
+                        }
71
+                    });
72
+                }
73
+                return;
74
+            }
75
+            String latestVersionUrl = JSONParseUtils.getJSONString(appInfo.getString("latest_url"));
76
+            URL url = new URL(latestVersionUrl);
77
+            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
78
+            conn.setRequestMethod("GET");
79
+            conn.setReadTimeout(10000);
80
+            conn.setConnectTimeout(10000);
81
+            conn.setRequestProperty("accept", "*/*");
82
+            conn.connect();
83
+
84
+            File dir = new File(Constants.APP_TEMP_DIR);
85
+            dir.mkdirs();
86
+            File file = new File(dir,"paiai"+System.currentTimeMillis()+".apk");
87
+            LogHelper.d(TAG,"需要更新,开始下载,下载到:"+file.getAbsolutePath());
88
+            if(!isMuteUpdate){
89
+                Toast.makeText(this, R.string.new_version_found,Toast.LENGTH_SHORT).show();
90
+            }
91
+            if(!file.exists()){
92
+                file.createNewFile();
93
+            }
94
+            FileOutputStream out = new FileOutputStream(file);
95
+            InputStream in =conn.getInputStream();
96
+            int bufSize = 8192;
97
+            int readLen = 0;
98
+            int pos = 0;
99
+            byte[] buffer = new byte[bufSize];
100
+            while((readLen=in.read(buffer,pos,bufSize-pos))!=-1){
101
+                out.write(buffer,pos,readLen);
102
+                pos+=readLen;
103
+                if(pos>=bufSize){
104
+                    pos = 0;
105
+                }
106
+            }
107
+            out.flush();
108
+            in.close();
109
+            out.close();
110
+            conn.disconnect();
111
+            Intent installIntent = new Intent();
112
+            installIntent.setAction(Intent.ACTION_VIEW);
113
+            installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
114
+            installIntent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
115
+            startActivity(installIntent);
116
+        }catch (Exception e){
117
+            if(!isMuteUpdate){
118
+                handler.post(new Runnable() {
119
+                    @Override
120
+                    public void run() {
121
+                        Toast.makeText(getApplicationContext(), R.string.check_version_fail,Toast.LENGTH_SHORT).show();
122
+                    }
123
+                });
124
+            }
125
+        }
126
+    }
127
+
128
+}

+ 2 - 2
app/src/main/java/ai/pai/lensman/uploadservice/UploadTask.java

@@ -1,4 +1,4 @@
1
-package ai.pai.lensman.uploadservice;
1
+package ai.pai.lensman.service;
2 2
 
3 3
 import android.content.Context;
4 4
 import android.os.AsyncTask;
@@ -39,7 +39,7 @@ public class UploadTask extends AsyncTask<String,Integer,Boolean> {
39 39
             if(photo.exists() &&photo.isFile()){
40 40
                 PhotoUploadUtils photoUploadUtils = new PhotoUploadUtils(UrlContainer.HOME_PHOTO_URL+"?timestamp="+System.currentTimeMillis());
41 41
                 photoUploadUtils.addFileParameter("photo", photo);
42
-                photoUploadUtils.addTextParameter("user_id", Preferences.getInstance(context).getUserId());
42
+                photoUploadUtils.addTextParameter("user_id", Preferences.getInstance(context).getLensManId());
43 43
                 photoUploadUtils.addTextParameter("group_id", groupId);
44 44
                 photoUploadUtils.addTextParameter("nickname", Preferences.getInstance(context).getUserName());
45 45
                 String result=new String(photoUploadUtils.send(),"UTF-8");

+ 2 - 2
app/src/main/java/ai/pai/lensman/session/SessionActivity.java

@@ -3,20 +3,20 @@ package ai.pai.lensman.session;
3 3
 import android.app.Activity;
4 4
 import android.content.Intent;
5 5
 import android.os.Bundle;
6
-import android.support.v7.app.AppCompatActivity;
7 6
 import android.support.v7.widget.LinearLayoutManager;
8 7
 import android.support.v7.widget.RecyclerView;
9 8
 import android.view.View;
10 9
 import android.widget.TextView;
11 10
 
12 11
 import ai.pai.lensman.R;
12
+import ai.pai.lensman.base.BaseActivity;
13 13
 import ai.pai.lensman.qrcode.QRCaptureActivity;
14 14
 import ai.pai.lensman.upload.SessionBean;
15 15
 import butterknife.BindView;
16 16
 import butterknife.ButterKnife;
17 17
 import butterknife.OnClick;
18 18
 
19
-public class SessionActivity extends AppCompatActivity implements SessionContract.SessionView {
19
+public class SessionActivity extends BaseActivity implements SessionContract.SessionView {
20 20
 
21 21
     @BindView(R.id.icon_no_data) View noPhotoLayout;
22 22
     @BindView(R.id.title_bar_middle_txt) TextView titleTextView;

+ 2 - 1
app/src/main/java/ai/pai/lensman/settings/SettingsActivity.java

@@ -5,13 +5,14 @@ import android.support.annotation.Nullable;
5 5
 import android.support.v7.app.AppCompatActivity;
6 6
 
7 7
 import ai.pai.lensman.R;
8
+import ai.pai.lensman.base.BaseActivity;
8 9
 import butterknife.ButterKnife;
9 10
 import butterknife.OnClick;
10 11
 
11 12
 /**
12 13
  * Created by chengzhenyu on 2016/7/21.
13 14
  */
14
-public class SettingsActivity extends AppCompatActivity {
15
+public class SettingsActivity extends BaseActivity {
15 16
 
16 17
 
17 18
     @Override

+ 70 - 0
app/src/main/java/ai/pai/lensman/splash/SplashActivity.java

@@ -0,0 +1,70 @@
1
+package ai.pai.lensman.splash;
2
+
3
+import android.content.Intent;
4
+import android.os.Bundle;
5
+import android.os.Handler;
6
+import android.support.v4.app.FragmentActivity;
7
+
8
+import com.android.common.utils.NetworkUtil;
9
+
10
+import java.text.SimpleDateFormat;
11
+import java.util.Date;
12
+
13
+import ai.pai.lensman.R;
14
+import ai.pai.lensman.db.Preferences;
15
+import ai.pai.lensman.login.LoginActivity;
16
+import ai.pai.lensman.service.UpgradeService;
17
+import ai.pai.lensman.upload.UploadActivity;
18
+
19
+public class SplashActivity extends FragmentActivity {
20
+
21
+    private Handler mHandler;
22
+    private static final int NORMAL_DELAY_TIME = 1500;
23
+
24
+    @Override
25
+    protected void onCreate(Bundle savedInstanceState) {
26
+        super.onCreate(savedInstanceState);
27
+        setContentView(R.layout.activity_splash);
28
+        mHandler = new Handler();
29
+    }
30
+
31
+    @Override
32
+    protected void onResume() {
33
+        super.onResume();
34
+        checkUpdateAndSplash();
35
+        mHandler.postDelayed(new Runnable() {
36
+            @Override
37
+            public void run() {
38
+                Intent intent ;
39
+                if(Preferences.getInstance(getApplicationContext()).getLensManId().isEmpty()){
40
+                    intent = new Intent(SplashActivity.this, LoginActivity.class);
41
+                }else{
42
+                    intent = new Intent(SplashActivity.this, UploadActivity.class);
43
+                }
44
+                startActivity(intent);
45
+                finish();
46
+            }
47
+        }, NORMAL_DELAY_TIME);
48
+    }
49
+
50
+    @Override
51
+    protected void onStop() {
52
+        super.onStop();
53
+        mHandler.removeCallbacksAndMessages(null);
54
+    }
55
+
56
+    private void checkUpdateAndSplash() {
57
+        try {
58
+            String curDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
59
+            if (!Preferences.getInstance(this).getLastRunDate().equals(curDate) && NetworkUtil.isWifiConnected(this)) {
60
+                Preferences.getInstance(this).setLastRunDate(curDate);
61
+                Intent intent = new Intent(this, UpgradeService.class);
62
+                startService(intent);
63
+            }
64
+        } catch (Exception e) {
65
+            e.printStackTrace();
66
+        }
67
+    }
68
+
69
+
70
+}

+ 2 - 2
app/src/main/java/ai/pai/lensman/upload/UploadActivity.java

@@ -2,7 +2,6 @@ package ai.pai.lensman.upload;
2 2
 
3 3
 import android.content.Intent;
4 4
 import android.os.Bundle;
5
-import android.support.v7.app.AppCompatActivity;
6 5
 import android.support.v7.widget.GridLayoutManager;
7 6
 import android.support.v7.widget.LinearLayoutManager;
8 7
 import android.support.v7.widget.RecyclerView;
@@ -13,12 +12,13 @@ import android.widget.TextView;
13 12
 import java.util.ArrayList;
14 13
 
15 14
 import ai.pai.lensman.R;
15
+import ai.pai.lensman.base.BaseActivity;
16 16
 import ai.pai.lensman.briefs.BriefsActivity;
17 17
 import butterknife.BindView;
18 18
 import butterknife.ButterKnife;
19 19
 import butterknife.OnClick;
20 20
 
21
-public class UploadActivity extends AppCompatActivity implements UploadContract.UploadView{
21
+public class UploadActivity extends BaseActivity implements UploadContract.UploadView{
22 22
 
23 23
     @BindView(R.id.tv_bt_status) TextView btStatusTextView;
24 24
     @BindView(R.id.iv_bt_status) ImageView btStatusImageView;

+ 2 - 0
app/src/main/java/ai/pai/lensman/utils/UrlContainer.java

@@ -9,4 +9,6 @@ public class UrlContainer {
9 9
 
10 10
     public static final String HOME_PHOTO_URL = HOST_URL+"pai2/home";
11 11
 
12
+    public static final String CHECK_UPDATE_URL = HOST_URL+"op/upgrade";
13
+
12 14
 }

+ 11 - 1
app/src/main/res/layout/activity_login.xml

@@ -1,6 +1,16 @@
1 1
 <?xml version="1.0" encoding="utf-8"?>
2 2
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 3
     android:orientation="vertical" android:layout_width="match_parent"
4
-    android:layout_height="match_parent">
4
+    android:layout_height="match_parent"
5
+    android:background="@color/background_white">
5 6
 
7
+    <Button
8
+        android:id="@+id/btn_login"
9
+        android:layout_width="match_parent"
10
+        android:layout_height="50dp"
11
+        android:layout_gravity="center"
12
+        android:gravity="center"
13
+        android:textSize="20sp"
14
+        android:textColor="@color/black"
15
+        android:text="登录"/>
6 16
 </LinearLayout>

+ 15 - 0
app/src/main/res/layout/activity_splash.xml

@@ -0,0 +1,15 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    android:orientation="vertical" android:layout_width="match_parent"
4
+    android:background="@color/white"
5
+    android:layout_height="match_parent">
6
+
7
+    <TextView
8
+        android:layout_width="match_parent"
9
+        android:layout_height="wrap_content"
10
+        android:textSize="40sp"
11
+        android:layout_gravity="center"
12
+        android:gravity="center"
13
+        android:textColor="@color/black"
14
+        android:text="@string/app_name"/>
15
+</LinearLayout>

+ 8 - 0
app/src/main/res/values/strings.xml

@@ -7,6 +7,8 @@
7 7
     <string name="settings">设置</string>
8 8
     <string name="scene">场景</string>
9 9
 
10
+    <string name="network_disconnect">当前无网络连接</string>
11
+
10 12
     <string name="qr_scan_hint">将取景框对准二维码,\n即可自动扫码</string>
11 13
     <string name="qr_scan_album">相册</string>
12 14
     <string name="scan_album_qr">选择二维码图片</string>
@@ -19,4 +21,10 @@
19 21
     <string name="check_permission_when_open_camera_error">相机打开出错,请检查是否已打开应用的相机权限</string>
20 22
 
21 23
     <string name="ok">确定</string>
24
+
25
+    <string name="current_version_is_latest">当前版本已是最新,无需升级</string>
26
+
27
+    <string name="check_version_fail">检查更新失败,请稍后重试</string>
28
+
29
+    <string name="new_version_found">发现新版本,正在更新中</string>
22 30
 </resources>

+ 0 - 1641
views/src/main/java/com/android/views/swipeLayout/SwipeLayout.java

@@ -1,1641 +0,0 @@
1
-package com.android.views.swipeLayout;
2
-
3
-import android.content.Context;
4
-import android.content.res.TypedArray;
5
-import android.graphics.Rect;
6
-import android.support.annotation.Nullable;
7
-import android.support.v4.view.GravityCompat;
8
-import android.support.v4.view.ViewCompat;
9
-import android.support.v4.widget.ViewDragHelper;
10
-import android.util.AttributeSet;
11
-import android.view.GestureDetector;
12
-import android.view.Gravity;
13
-import android.view.HapticFeedbackConstants;
14
-import android.view.MotionEvent;
15
-import android.view.View;
16
-import android.view.ViewConfiguration;
17
-import android.view.ViewGroup;
18
-import android.view.ViewParent;
19
-import android.widget.AbsListView;
20
-import android.widget.AdapterView;
21
-import android.widget.FrameLayout;
22
-
23
-import com.android.views.R;
24
-
25
-import java.lang.reflect.Method;
26
-import java.util.ArrayList;
27
-import java.util.Arrays;
28
-import java.util.HashMap;
29
-import java.util.LinkedHashMap;
30
-import java.util.List;
31
-import java.util.Map;
32
-
33
-public class SwipeLayout extends FrameLayout {
34
-
35
-    private static final int DRAG_LEFT = 1;
36
-    private static final int DRAG_RIGHT = 2;
37
-    private static final int DRAG_TOP = 4;
38
-    private static final int DRAG_BOTTOM = 8;
39
-    private static final DragEdge DefaultDragEdge = DragEdge.Right;
40
-
41
-    private int mTouchSlop;
42
-
43
-    private DragEdge mCurrentDragEdge = DefaultDragEdge;
44
-    private ViewDragHelper mDragHelper;
45
-
46
-    private int mDragDistance = 0;
47
-    private LinkedHashMap<DragEdge, View> mDragEdges = new LinkedHashMap<>();
48
-    private ShowMode mShowMode;
49
-
50
-    private float[] mEdgeSwipesOffset = new float[4];
51
-
52
-    private List<SwipeListener> mSwipeListeners = new ArrayList<>();
53
-    private List<SwipeDenier> mSwipeDeniers = new ArrayList<>();
54
-    private Map<View, ArrayList<OnRevealListener>> mRevealListeners = new HashMap<>();
55
-    private Map<View, Boolean> mShowEntirely = new HashMap<>();
56
-    private Map<View, Rect> mViewBoundCache = new HashMap<>();//save all children's bound, restore in onLayout
57
-
58
-    private DoubleClickListener mDoubleClickListener;
59
-
60
-    private boolean mSwipeEnabled = true;
61
-    private boolean[] mSwipesEnabled = new boolean[]{true, true, true, true};
62
-    private boolean mClickToClose = false;
63
-    private float mWillOpenPercentAfterOpen=0.75f;
64
-    private float mWillOpenPercentAfterClose=0.25f;
65
-
66
-    public enum DragEdge {
67
-        Left,
68
-        Top,
69
-        Right,
70
-        Bottom
71
-    }
72
-
73
-    public enum ShowMode {
74
-        LayDown,
75
-        PullOut
76
-    }
77
-
78
-    public SwipeLayout(Context context) {
79
-        this(context, null);
80
-    }
81
-
82
-    public SwipeLayout(Context context, AttributeSet attrs) {
83
-        this(context, attrs, 0);
84
-    }
85
-
86
-    public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
87
-        super(context, attrs, defStyle);
88
-        mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
89
-        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
90
-
91
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeLayout);
92
-        int dragEdgeChoices = a.getInt(R.styleable.SwipeLayout_drag_edge, DRAG_RIGHT);
93
-        mEdgeSwipesOffset[DragEdge.Left.ordinal()] = a.getDimension(R.styleable.SwipeLayout_leftEdgeSwipeOffset, 0);
94
-        mEdgeSwipesOffset[DragEdge.Right.ordinal()] = a.getDimension(R.styleable.SwipeLayout_rightEdgeSwipeOffset, 0);
95
-        mEdgeSwipesOffset[DragEdge.Top.ordinal()] = a.getDimension(R.styleable.SwipeLayout_topEdgeSwipeOffset, 0);
96
-        mEdgeSwipesOffset[DragEdge.Bottom.ordinal()] = a.getDimension(R.styleable.SwipeLayout_bottomEdgeSwipeOffset, 0);
97
-        setClickToClose(a.getBoolean(R.styleable.SwipeLayout_clickToClose, mClickToClose));
98
-
99
-        if ((dragEdgeChoices & DRAG_LEFT) == DRAG_LEFT) {
100
-            mDragEdges.put(DragEdge.Left, null);
101
-        }
102
-        if ((dragEdgeChoices & DRAG_TOP) == DRAG_TOP) {
103
-            mDragEdges.put(DragEdge.Top, null);
104
-        }
105
-        if ((dragEdgeChoices & DRAG_RIGHT) == DRAG_RIGHT) {
106
-            mDragEdges.put(DragEdge.Right, null);
107
-        }
108
-        if ((dragEdgeChoices & DRAG_BOTTOM) == DRAG_BOTTOM) {
109
-            mDragEdges.put(DragEdge.Bottom, null);
110
-        }
111
-        int ordinal = a.getInt(R.styleable.SwipeLayout_show_mode, ShowMode.PullOut.ordinal());
112
-        mShowMode = ShowMode.values()[ordinal];
113
-        a.recycle();
114
-
115
-    }
116
-
117
-    public interface SwipeListener {
118
-        void onStartOpen(SwipeLayout layout);
119
-
120
-        void onOpen(SwipeLayout layout);
121
-
122
-        void onStartClose(SwipeLayout layout);
123
-
124
-        void onClose(SwipeLayout layout);
125
-
126
-        void onUpdate(SwipeLayout layout, int leftOffset, int topOffset);
127
-
128
-        void onHandRelease(SwipeLayout layout, float xvel, float yvel);
129
-    }
130
-
131
-    public void addSwipeListener(SwipeListener l) {
132
-        mSwipeListeners.add(l);
133
-    }
134
-
135
-    public void removeSwipeListener(SwipeListener l) {
136
-        mSwipeListeners.remove(l);
137
-    }
138
-
139
-    public void removeAllSwipeListener() {
140
-        mSwipeListeners.clear();
141
-    }
142
-
143
-    public interface SwipeDenier {
144
-        /*
145
-         * Called in onInterceptTouchEvent Determines if this swipe event should
146
-         * be denied Implement this interface if you are using views with swipe
147
-         * gestures As a child of SwipeLayout
148
-         * 
149
-         * @return true deny false allow
150
-         */
151
-        boolean shouldDenySwipe(MotionEvent ev);
152
-    }
153
-
154
-    public void addSwipeDenier(SwipeDenier denier) {
155
-        mSwipeDeniers.add(denier);
156
-    }
157
-
158
-    public void removeSwipeDenier(SwipeDenier denier) {
159
-        mSwipeDeniers.remove(denier);
160
-    }
161
-
162
-    public void removeAllSwipeDeniers() {
163
-        mSwipeDeniers.clear();
164
-    }
165
-
166
-    public interface OnRevealListener {
167
-        void onReveal(View child, DragEdge edge, float fraction, int distance);
168
-    }
169
-
170
-    /**
171
-     * bind a view with a specific
172
-     */
173
-    public void addRevealListener(int childId, OnRevealListener l) {
174
-        View child = findViewById(childId);
175
-        if (child == null) {
176
-            throw new IllegalArgumentException("Child does not belong to SwipeListener.");
177
-        }
178
-
179
-        if (!mShowEntirely.containsKey(child)) {
180
-            mShowEntirely.put(child, false);
181
-        }
182
-        if (mRevealListeners.get(child) == null)
183
-            mRevealListeners.put(child, new ArrayList<OnRevealListener>());
184
-
185
-        mRevealListeners.get(child).add(l);
186
-    }
187
-
188
-    /**
189
-     * bind multiple views with an
190
-     */
191
-    public void addRevealListener(int[] childIds, OnRevealListener l) {
192
-        for (int i : childIds)
193
-            addRevealListener(i, l);
194
-    }
195
-
196
-    public void removeRevealListener(int childId, OnRevealListener l) {
197
-        View child = findViewById(childId);
198
-
199
-        if (child == null) return;
200
-
201
-        mShowEntirely.remove(child);
202
-        if (mRevealListeners.containsKey(child)) mRevealListeners.get(child).remove(l);
203
-    }
204
-
205
-    public void removeAllRevealListeners(int childId) {
206
-        View child = findViewById(childId);
207
-        if (child != null) {
208
-            mRevealListeners.remove(child);
209
-            mShowEntirely.remove(child);
210
-        }
211
-    }
212
-
213
-    private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
214
-
215
-        @Override
216
-        public int clampViewPositionHorizontal(View child, int left, int dx) {
217
-            if (child == getSurfaceView()) {
218
-                switch (mCurrentDragEdge) {
219
-                    case Top:
220
-                    case Bottom:
221
-                        return getPaddingLeft();
222
-                    case Left:
223
-                        if (left < getPaddingLeft()) return getPaddingLeft();
224
-                        if (left > getPaddingLeft() + mDragDistance)
225
-                            return getPaddingLeft() + mDragDistance;
226
-                        break;
227
-                    case Right:
228
-                        if (left > getPaddingLeft()) return getPaddingLeft();
229
-                        if (left < getPaddingLeft() - mDragDistance)
230
-                            return getPaddingLeft() - mDragDistance;
231
-                        break;
232
-                }
233
-            } else if (getCurrentBottomView() == child) {
234
-
235
-                switch (mCurrentDragEdge) {
236
-                    case Top:
237
-                    case Bottom:
238
-                        return getPaddingLeft();
239
-                    case Left:
240
-                        if (mShowMode == ShowMode.PullOut) {
241
-                            if (left > getPaddingLeft()) return getPaddingLeft();
242
-                        }
243
-                        break;
244
-                    case Right:
245
-                        if (mShowMode == ShowMode.PullOut) {
246
-                            if (left < getMeasuredWidth() - mDragDistance) {
247
-                                return getMeasuredWidth() - mDragDistance;
248
-                            }
249
-                        }
250
-                        break;
251
-                }
252
-            }
253
-            return left;
254
-        }
255
-
256
-        @Override
257
-        public int clampViewPositionVertical(View child, int top, int dy) {
258
-            if (child == getSurfaceView()) {
259
-                switch (mCurrentDragEdge) {
260
-                    case Left:
261
-                    case Right:
262
-                        return getPaddingTop();
263
-                    case Top:
264
-                        if (top < getPaddingTop()) return getPaddingTop();
265
-                        if (top > getPaddingTop() + mDragDistance)
266
-                            return getPaddingTop() + mDragDistance;
267
-                        break;
268
-                    case Bottom:
269
-                        if (top < getPaddingTop() - mDragDistance) {
270
-                            return getPaddingTop() - mDragDistance;
271
-                        }
272
-                        if (top > getPaddingTop()) {
273
-                            return getPaddingTop();
274
-                        }
275
-                }
276
-            } else {
277
-                View surfaceView = getSurfaceView();
278
-                int surfaceViewTop = surfaceView == null ? 0 : surfaceView.getTop();
279
-                switch (mCurrentDragEdge) {
280
-                    case Left:
281
-                    case Right:
282
-                        return getPaddingTop();
283
-                    case Top:
284
-                        if (mShowMode == ShowMode.PullOut) {
285
-                            if (top > getPaddingTop()) return getPaddingTop();
286
-                        } else {
287
-                            if (surfaceViewTop + dy < getPaddingTop())
288
-                                return getPaddingTop();
289
-                            if (surfaceViewTop + dy > getPaddingTop() + mDragDistance)
290
-                                return getPaddingTop() + mDragDistance;
291
-                        }
292
-                        break;
293
-                    case Bottom:
294
-                        if (mShowMode == ShowMode.PullOut) {
295
-                            if (top < getMeasuredHeight() - mDragDistance)
296
-                                return getMeasuredHeight() - mDragDistance;
297
-                        } else {
298
-                            if (surfaceViewTop + dy >= getPaddingTop())
299
-                                return getPaddingTop();
300
-                            if (surfaceViewTop + dy <= getPaddingTop() - mDragDistance)
301
-                                return getPaddingTop() - mDragDistance;
302
-                        }
303
-                }
304
-            }
305
-            return top;
306
-        }
307
-
308
-        @Override
309
-        public boolean tryCaptureView(View child, int pointerId) {
310
-            boolean result = child == getSurfaceView() || getBottomViews().contains(child);
311
-            if (result) {
312
-                isCloseBeforeDrag = getOpenStatus() == Status.Close;
313
-            }
314
-            return result;
315
-        }
316
-
317
-        @Override
318
-        public int getViewHorizontalDragRange(View child) {
319
-            return mDragDistance;
320
-        }
321
-
322
-        @Override
323
-        public int getViewVerticalDragRange(View child) {
324
-            return mDragDistance;
325
-        }
326
-
327
-        boolean isCloseBeforeDrag = true;
328
-
329
-        @Override
330
-        public void onViewReleased(View releasedChild, float xvel, float yvel) {
331
-            super.onViewReleased(releasedChild, xvel, yvel);
332
-            processHandRelease(xvel, yvel, isCloseBeforeDrag);
333
-            for (SwipeListener l : mSwipeListeners) {
334
-                l.onHandRelease(SwipeLayout.this, xvel, yvel);
335
-            }
336
-
337
-            invalidate();
338
-        }
339
-
340
-        @Override
341
-        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
342
-            View surfaceView = getSurfaceView();
343
-            if (surfaceView == null) return;
344
-            View currentBottomView = getCurrentBottomView();
345
-            int evLeft = surfaceView.getLeft(),
346
-                    evRight = surfaceView.getRight(),
347
-                    evTop = surfaceView.getTop(),
348
-                    evBottom = surfaceView.getBottom();
349
-            if (changedView == surfaceView) {
350
-
351
-                if (mShowMode == ShowMode.PullOut && currentBottomView != null) {
352
-                    if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) {
353
-                        currentBottomView.offsetLeftAndRight(dx);
354
-                    } else {
355
-                        currentBottomView.offsetTopAndBottom(dy);
356
-                    }
357
-                }
358
-
359
-            } else if (getBottomViews().contains(changedView)) {
360
-
361
-                if (mShowMode == ShowMode.PullOut) {
362
-                    surfaceView.offsetLeftAndRight(dx);
363
-                    surfaceView.offsetTopAndBottom(dy);
364
-                } else {
365
-                    Rect rect = computeBottomLayDown(mCurrentDragEdge);
366
-                    if (currentBottomView != null) {
367
-                        currentBottomView.layout(rect.left, rect.top, rect.right, rect.bottom);
368
-                    }
369
-
370
-                    int newLeft = surfaceView.getLeft() + dx, newTop = surfaceView.getTop() + dy;
371
-
372
-                    if (mCurrentDragEdge == DragEdge.Left && newLeft < getPaddingLeft())
373
-                        newLeft = getPaddingLeft();
374
-                    else if (mCurrentDragEdge == DragEdge.Right && newLeft > getPaddingLeft())
375
-                        newLeft = getPaddingLeft();
376
-                    else if (mCurrentDragEdge == DragEdge.Top && newTop < getPaddingTop())
377
-                        newTop = getPaddingTop();
378
-                    else if (mCurrentDragEdge == DragEdge.Bottom && newTop > getPaddingTop())
379
-                        newTop = getPaddingTop();
380
-
381
-                    surfaceView.layout(newLeft, newTop, newLeft + getMeasuredWidth(), newTop + getMeasuredHeight());
382
-                }
383
-            }
384
-
385
-            dispatchRevealEvent(evLeft, evTop, evRight, evBottom);
386
-
387
-            dispatchSwipeEvent(evLeft, evTop, dx, dy);
388
-
389
-            invalidate();
390
-
391
-            captureChildrenBound();
392
-        }
393
-    };
394
-
395
-    /**
396
-     * save children's bounds, so they can restore the bound in {@link #onLayout(boolean, int, int, int, int)}
397
-     */
398
-    private void captureChildrenBound(){
399
-        View currentBottomView = getCurrentBottomView();
400
-        if(getOpenStatus()== Status.Close){
401
-            mViewBoundCache.remove(currentBottomView);
402
-            return;
403
-        }
404
-
405
-        View[] views = new View[]{getSurfaceView(), currentBottomView};
406
-        for (View child : views) {
407
-            Rect rect = mViewBoundCache.get(child);
408
-            if(rect==null){
409
-                rect = new Rect();
410
-                mViewBoundCache.put(child, rect);
411
-            }
412
-            rect.left = child.getLeft();
413
-            rect.top = child.getTop();
414
-            rect.right = child.getRight();
415
-            rect.bottom = child.getBottom();
416
-        }
417
-    }
418
-
419
-    /**
420
-     * the dispatchRevealEvent method may not always get accurate position, it
421
-     * makes the view may not always get the event when the view is totally
422
-     * show( fraction = 1), so , we need to calculate every time.
423
-     */
424
-    protected boolean isViewTotallyFirstShowed(View child, Rect relativePosition, DragEdge edge, int surfaceLeft,
425
-                                               int surfaceTop, int surfaceRight, int surfaceBottom) {
426
-        if (mShowEntirely.get(child)) return false;
427
-        int childLeft = relativePosition.left;
428
-        int childRight = relativePosition.right;
429
-        int childTop = relativePosition.top;
430
-        int childBottom = relativePosition.bottom;
431
-        boolean r = false;
432
-        if (getShowMode() == ShowMode.LayDown) {
433
-            if ((edge == DragEdge.Right && surfaceRight <= childLeft)
434
-                    || (edge == DragEdge.Left && surfaceLeft >= childRight)
435
-                    || (edge == DragEdge.Top && surfaceTop >= childBottom)
436
-                    || (edge == DragEdge.Bottom && surfaceBottom <= childTop)) r = true;
437
-        } else if (getShowMode() == ShowMode.PullOut) {
438
-            if ((edge == DragEdge.Right && childRight <= getWidth())
439
-                    || (edge == DragEdge.Left && childLeft >= getPaddingLeft())
440
-                    || (edge == DragEdge.Top && childTop >= getPaddingTop())
441
-                    || (edge == DragEdge.Bottom && childBottom <= getHeight())) r = true;
442
-        }
443
-        return r;
444
-    }
445
-
446
-    protected boolean isViewShowing(View child, Rect relativePosition, DragEdge availableEdge, int surfaceLeft,
447
-                                    int surfaceTop, int surfaceRight, int surfaceBottom) {
448
-        int childLeft = relativePosition.left;
449
-        int childRight = relativePosition.right;
450
-        int childTop = relativePosition.top;
451
-        int childBottom = relativePosition.bottom;
452
-        if (getShowMode() == ShowMode.LayDown) {
453
-            switch (availableEdge) {
454
-                case Right:
455
-                    if (surfaceRight > childLeft && surfaceRight <= childRight) {
456
-                        return true;
457
-                    }
458
-                    break;
459
-                case Left:
460
-                    if (surfaceLeft < childRight && surfaceLeft >= childLeft) {
461
-                        return true;
462
-                    }
463
-                    break;
464
-                case Top:
465
-                    if (surfaceTop >= childTop && surfaceTop < childBottom) {
466
-                        return true;
467
-                    }
468
-                    break;
469
-                case Bottom:
470
-                    if (surfaceBottom > childTop && surfaceBottom <= childBottom) {
471
-                        return true;
472
-                    }
473
-                    break;
474
-            }
475
-        } else if (getShowMode() == ShowMode.PullOut) {
476
-            switch (availableEdge) {
477
-                case Right:
478
-                    if (childLeft <= getWidth() && childRight > getWidth()) return true;
479
-                    break;
480
-                case Left:
481
-                    if (childRight >= getPaddingLeft() && childLeft < getPaddingLeft()) return true;
482
-                    break;
483
-                case Top:
484
-                    if (childTop < getPaddingTop() && childBottom >= getPaddingTop()) return true;
485
-                    break;
486
-                case Bottom:
487
-                    if (childTop < getHeight() && childTop >= getPaddingTop()) return true;
488
-                    break;
489
-            }
490
-        }
491
-        return false;
492
-    }
493
-
494
-    protected Rect getRelativePosition(View child) {
495
-        View t = child;
496
-        Rect r = new Rect(t.getLeft(), t.getTop(), 0, 0);
497
-        while (t.getParent() != null && t != getRootView()) {
498
-            t = (View) t.getParent();
499
-            if (t == this) break;
500
-            r.left += t.getLeft();
501
-            r.top += t.getTop();
502
-        }
503
-        r.right = r.left + child.getMeasuredWidth();
504
-        r.bottom = r.top + child.getMeasuredHeight();
505
-        return r;
506
-    }
507
-
508
-    private int mEventCounter = 0;
509
-
510
-    protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, int dx, int dy) {
511
-        DragEdge edge = getDragEdge();
512
-        boolean open = true;
513
-        if (edge == DragEdge.Left) {
514
-            if (dx < 0) open = false;
515
-        } else if (edge == DragEdge.Right) {
516
-            if (dx > 0) open = false;
517
-        } else if (edge == DragEdge.Top) {
518
-            if (dy < 0) open = false;
519
-        } else if (edge == DragEdge.Bottom) {
520
-            if (dy > 0) open = false;
521
-        }
522
-
523
-        dispatchSwipeEvent(surfaceLeft, surfaceTop, open);
524
-    }
525
-
526
-    protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, boolean open) {
527
-        safeBottomView();
528
-        Status status = getOpenStatus();
529
-
530
-        if (!mSwipeListeners.isEmpty()) {
531
-            mEventCounter++;
532
-            for (SwipeListener l : mSwipeListeners) {
533
-                if (mEventCounter == 1) {
534
-                    if (open) {
535
-                        l.onStartOpen(this);
536
-                    } else {
537
-                        l.onStartClose(this);
538
-                    }
539
-                }
540
-                l.onUpdate(SwipeLayout.this, surfaceLeft - getPaddingLeft(), surfaceTop - getPaddingTop());
541
-            }
542
-
543
-            if (status == Status.Close) {
544
-                for (SwipeListener l : mSwipeListeners) {
545
-                    l.onClose(SwipeLayout.this);
546
-                }
547
-                mEventCounter = 0;
548
-            }
549
-
550
-            if (status == Status.Open) {
551
-                View currentBottomView = getCurrentBottomView();
552
-                if (currentBottomView != null) {
553
-                    currentBottomView.setEnabled(true);
554
-                }
555
-                for (SwipeListener l : mSwipeListeners) {
556
-                    l.onOpen(SwipeLayout.this);
557
-                }
558
-                mEventCounter = 0;
559
-            }
560
-        }
561
-    }
562
-
563
-    /**
564
-     * prevent bottom view get any touch event. Especially in LayDown mode.
565
-     */
566
-    private void safeBottomView() {
567
-        Status status = getOpenStatus();
568
-        List<View> bottoms = getBottomViews();
569
-
570
-        if (status == Status.Close) {
571
-            for (View bottom : bottoms) {
572
-                if (bottom != null && bottom.getVisibility() != INVISIBLE) {
573
-                    bottom.setVisibility(INVISIBLE);
574
-                }
575
-            }
576
-        } else {
577
-            View currentBottomView = getCurrentBottomView();
578
-            if (currentBottomView != null && currentBottomView.getVisibility() != VISIBLE) {
579
-                currentBottomView.setVisibility(VISIBLE);
580
-            }
581
-        }
582
-    }
583
-
584
-    protected void dispatchRevealEvent(final int surfaceLeft, final int surfaceTop, final int surfaceRight,
585
-                                       final int surfaceBottom) {
586
-        if (mRevealListeners.isEmpty()) return;
587
-        for (Map.Entry<View, ArrayList<OnRevealListener>> entry : mRevealListeners.entrySet()) {
588
-            View child = entry.getKey();
589
-            Rect rect = getRelativePosition(child);
590
-            if (isViewShowing(child, rect, mCurrentDragEdge, surfaceLeft, surfaceTop,
591
-                    surfaceRight, surfaceBottom)) {
592
-                mShowEntirely.put(child, false);
593
-                int distance = 0;
594
-                float fraction = 0f;
595
-                if (getShowMode() == ShowMode.LayDown) {
596
-                    switch (mCurrentDragEdge) {
597
-                        case Left:
598
-                            distance = rect.left - surfaceLeft;
599
-                            fraction = distance / (float) child.getWidth();
600
-                            break;
601
-                        case Right:
602
-                            distance = rect.right - surfaceRight;
603
-                            fraction = distance / (float) child.getWidth();
604
-                            break;
605
-                        case Top:
606
-                            distance = rect.top - surfaceTop;
607
-                            fraction = distance / (float) child.getHeight();
608
-                            break;
609
-                        case Bottom:
610
-                            distance = rect.bottom - surfaceBottom;
611
-                            fraction = distance / (float) child.getHeight();
612
-                            break;
613
-                    }
614
-                } else if (getShowMode() == ShowMode.PullOut) {
615
-                    switch (mCurrentDragEdge) {
616
-                        case Left:
617
-                            distance = rect.right - getPaddingLeft();
618
-                            fraction = distance / (float) child.getWidth();
619
-                            break;
620
-                        case Right:
621
-                            distance = rect.left - getWidth();
622
-                            fraction = distance / (float) child.getWidth();
623
-                            break;
624
-                        case Top:
625
-                            distance = rect.bottom - getPaddingTop();
626
-                            fraction = distance / (float) child.getHeight();
627
-                            break;
628
-                        case Bottom:
629
-                            distance = rect.top - getHeight();
630
-                            fraction = distance / (float) child.getHeight();
631
-                            break;
632
-                    }
633
-                }
634
-
635
-                for (OnRevealListener l : entry.getValue()) {
636
-                    l.onReveal(child, mCurrentDragEdge, Math.abs(fraction), distance);
637
-                    if (Math.abs(fraction) == 1) {
638
-                        mShowEntirely.put(child, true);
639
-                    }
640
-                }
641
-            }
642
-
643
-            if (isViewTotallyFirstShowed(child, rect, mCurrentDragEdge, surfaceLeft, surfaceTop,
644
-                    surfaceRight, surfaceBottom)) {
645
-                mShowEntirely.put(child, true);
646
-                for (OnRevealListener l : entry.getValue()) {
647
-                    if (mCurrentDragEdge == DragEdge.Left
648
-                            || mCurrentDragEdge == DragEdge.Right)
649
-                        l.onReveal(child, mCurrentDragEdge, 1, child.getWidth());
650
-                    else
651
-                        l.onReveal(child, mCurrentDragEdge, 1, child.getHeight());
652
-                }
653
-            }
654
-
655
-        }
656
-    }
657
-
658
-    @Override
659
-    public void computeScroll() {
660
-        super.computeScroll();
661
-        if (mDragHelper.continueSettling(true)) {
662
-            ViewCompat.postInvalidateOnAnimation(this);
663
-        }
664
-    }
665
-
666
-    /**
667
-     * {@link OnLayoutChangeListener} added in API 11. I need
668
-     * to support it from API 8.
669
-     */
670
-    public interface OnLayout {
671
-        void onLayout(SwipeLayout v);
672
-    }
673
-
674
-    private List<OnLayout> mOnLayoutListeners;
675
-
676
-    public void addOnLayoutListener(OnLayout l) {
677
-        if (mOnLayoutListeners == null) mOnLayoutListeners = new ArrayList<OnLayout>();
678
-        mOnLayoutListeners.add(l);
679
-    }
680
-
681
-    public void removeOnLayoutListener(OnLayout l) {
682
-        if (mOnLayoutListeners != null) mOnLayoutListeners.remove(l);
683
-    }
684
-
685
-    public void clearDragEdge() {
686
-        mDragEdges.clear();
687
-    }
688
-
689
-    public void setDrag(DragEdge dragEdge, int childId) {
690
-        clearDragEdge();
691
-        addDrag(dragEdge, childId);
692
-    }
693
-
694
-    public void setDrag(DragEdge dragEdge, View child) {
695
-        clearDragEdge();
696
-        addDrag(dragEdge, child);
697
-    }
698
-
699
-    public void addDrag(DragEdge dragEdge, int childId) {
700
-        addDrag(dragEdge, findViewById(childId), null);
701
-    }
702
-
703
-    public void addDrag(DragEdge dragEdge, View child) {
704
-        addDrag(dragEdge, child, null);
705
-    }
706
-
707
-    public void addDrag(DragEdge dragEdge, View child, ViewGroup.LayoutParams params) {
708
-        if (child == null) return;
709
-
710
-        if (params == null) {
711
-            params = generateDefaultLayoutParams();
712
-        }
713
-        if (!checkLayoutParams(params)) {
714
-            params = generateLayoutParams(params);
715
-        }
716
-        int gravity = -1;
717
-        switch (dragEdge) {
718
-            case Left:
719
-                gravity = Gravity.LEFT;
720
-                break;
721
-            case Right:
722
-                gravity = Gravity.RIGHT;
723
-                break;
724
-            case Top:
725
-                gravity = Gravity.TOP;
726
-                break;
727
-            case Bottom:
728
-                gravity = Gravity.BOTTOM;
729
-                break;
730
-        }
731
-        if (params instanceof LayoutParams) {
732
-            ((LayoutParams) params).gravity = gravity;
733
-        }
734
-        addView(child, 0, params);
735
-    }
736
-
737
-    @Override
738
-    public void addView(View child, int index, ViewGroup.LayoutParams params) {
739
-        if (child == null) return;
740
-        int gravity = Gravity.NO_GRAVITY;
741
-        try {
742
-            gravity = (Integer) params.getClass().getField("gravity").get(params);
743
-        } catch (Exception e) {
744
-            e.printStackTrace();
745
-        }
746
-
747
-        if (gravity > 0) {
748
-            gravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
749
-
750
-            if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
751
-                mDragEdges.put(DragEdge.Left, child);
752
-            }
753
-            if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
754
-                mDragEdges.put(DragEdge.Right, child);
755
-            }
756
-            if ((gravity & Gravity.TOP) == Gravity.TOP) {
757
-                mDragEdges.put(DragEdge.Top, child);
758
-            }
759
-            if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
760
-                mDragEdges.put(DragEdge.Bottom, child);
761
-            }
762
-        } else {
763
-            for (Map.Entry<DragEdge, View> entry : mDragEdges.entrySet()) {
764
-                if (entry.getValue() == null) {
765
-                    //means used the drag_edge attr, the no gravity child should be use set
766
-                    mDragEdges.put(entry.getKey(), child);
767
-                    break;
768
-                }
769
-            }
770
-        }
771
-        if (child.getParent() == this) {
772
-            return;
773
-        }
774
-        super.addView(child, index, params);
775
-    }
776
-
777
-    @Override
778
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
779
-        updateBottomViews();
780
-
781
-        if (mOnLayoutListeners != null) for (int i = 0; i < mOnLayoutListeners.size(); i++) {
782
-            mOnLayoutListeners.get(i).onLayout(this);
783
-        }
784
-    }
785
-
786
-    void layoutPullOut() {
787
-        View surfaceView = getSurfaceView();
788
-        Rect surfaceRect = mViewBoundCache.get(surfaceView);
789
-        if(surfaceRect == null) surfaceRect = computeSurfaceLayoutArea(false);
790
-        if (surfaceView != null) {
791
-            surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom);
792
-            bringChildToFront(surfaceView);
793
-        }
794
-        View currentBottomView = getCurrentBottomView();
795
-        Rect bottomViewRect = mViewBoundCache.get(currentBottomView);
796
-        if(bottomViewRect == null) bottomViewRect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, surfaceRect);
797
-        if (currentBottomView != null) {
798
-            currentBottomView.layout(bottomViewRect.left, bottomViewRect.top, bottomViewRect.right, bottomViewRect.bottom);
799
-        }
800
-    }
801
-
802
-    void layoutLayDown() {
803
-        View surfaceView = getSurfaceView();
804
-        Rect surfaceRect = mViewBoundCache.get(surfaceView);
805
-        if(surfaceRect == null) surfaceRect = computeSurfaceLayoutArea(false);
806
-        if (surfaceView != null) {
807
-            surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom);
808
-            bringChildToFront(surfaceView);
809
-        }
810
-        View currentBottomView = getCurrentBottomView();
811
-        Rect bottomViewRect = mViewBoundCache.get(currentBottomView);
812
-        if(bottomViewRect == null) bottomViewRect = computeBottomLayoutAreaViaSurface(ShowMode.LayDown, surfaceRect);
813
-        if (currentBottomView != null) {
814
-            currentBottomView.layout(bottomViewRect.left, bottomViewRect.top, bottomViewRect.right, bottomViewRect.bottom);
815
-        }
816
-    }
817
-
818
-    private boolean mIsBeingDragged;
819
-
820
-    private void checkCanDrag(MotionEvent ev) {
821
-        if (mIsBeingDragged) return;
822
-        if (getOpenStatus() == Status.Middle) {
823
-            mIsBeingDragged = true;
824
-            return;
825
-        }
826
-        Status status = getOpenStatus();
827
-        float distanceX = ev.getRawX() - sX;
828
-        float distanceY = ev.getRawY() - sY;
829
-        float angle = Math.abs(distanceY / distanceX);
830
-        angle = (float) Math.toDegrees(Math.atan(angle));
831
-        if (getOpenStatus() == Status.Close) {
832
-            DragEdge dragEdge;
833
-            if (angle < 45) {
834
-                if (distanceX > 0 && isLeftSwipeEnabled()) {
835
-                    dragEdge = DragEdge.Left;
836
-                } else if (distanceX < 0 && isRightSwipeEnabled()) {
837
-                    dragEdge = DragEdge.Right;
838
-                } else return;
839
-
840
-            } else {
841
-                if (distanceY > 0 && isTopSwipeEnabled()) {
842
-                    dragEdge = DragEdge.Top;
843
-                } else if (distanceY < 0 && isBottomSwipeEnabled()) {
844
-                    dragEdge = DragEdge.Bottom;
845
-                } else return;
846
-            }
847
-            setCurrentDragEdge(dragEdge);
848
-        }
849
-
850
-        boolean doNothing = false;
851
-        if (mCurrentDragEdge == DragEdge.Right) {
852
-            boolean suitable = (status == Status.Open && distanceX > mTouchSlop)
853
-                    || (status == Status.Close && distanceX < -mTouchSlop);
854
-            suitable = suitable || (status == Status.Middle);
855
-
856
-            if (angle > 30 || !suitable) {
857
-                doNothing = true;
858
-            }
859
-        }
860
-
861
-        if (mCurrentDragEdge == DragEdge.Left) {
862
-            boolean suitable = (status == Status.Open && distanceX < -mTouchSlop)
863
-                    || (status == Status.Close && distanceX > mTouchSlop);
864
-            suitable = suitable || status == Status.Middle;
865
-
866
-            if (angle > 30 || !suitable) {
867
-                doNothing = true;
868
-            }
869
-        }
870
-
871
-        if (mCurrentDragEdge == DragEdge.Top) {
872
-            boolean suitable = (status == Status.Open && distanceY < -mTouchSlop)
873
-                    || (status == Status.Close && distanceY > mTouchSlop);
874
-            suitable = suitable || status == Status.Middle;
875
-
876
-            if (angle < 60 || !suitable) {
877
-                doNothing = true;
878
-            }
879
-        }
880
-
881
-        if (mCurrentDragEdge == DragEdge.Bottom) {
882
-            boolean suitable = (status == Status.Open && distanceY > mTouchSlop)
883
-                    || (status == Status.Close && distanceY < -mTouchSlop);
884
-            suitable = suitable || status == Status.Middle;
885
-
886
-            if (angle < 60 || !suitable) {
887
-                doNothing = true;
888
-            }
889
-        }
890
-        mIsBeingDragged = !doNothing;
891
-    }
892
-
893
-    @Override
894
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
895
-        if (!isSwipeEnabled()) {
896
-            return false;
897
-        }
898
-        if (mClickToClose && getOpenStatus() == Status.Open && isTouchOnSurface(ev)) {
899
-            return true;
900
-        }
901
-        for (SwipeDenier denier : mSwipeDeniers) {
902
-            if (denier != null && denier.shouldDenySwipe(ev)) {
903
-                return false;
904
-            }
905
-        }
906
-
907
-        switch (ev.getAction()) {
908
-            case MotionEvent.ACTION_DOWN:
909
-                mDragHelper.processTouchEvent(ev);
910
-                mIsBeingDragged = false;
911
-                sX = ev.getRawX();
912
-                sY = ev.getRawY();
913
-                //if the swipe is in middle state(scrolling), should intercept the touch
914
-                if (getOpenStatus() == Status.Middle) {
915
-                    mIsBeingDragged = true;
916
-                }
917
-                break;
918
-            case MotionEvent.ACTION_MOVE:
919
-                boolean beforeCheck = mIsBeingDragged;
920
-                checkCanDrag(ev);
921
-                if (mIsBeingDragged) {
922
-                    ViewParent parent = getParent();
923
-                    if (parent != null) {
924
-                        parent.requestDisallowInterceptTouchEvent(true);
925
-                    }
926
-                }
927
-                if (!beforeCheck && mIsBeingDragged) {
928
-                    //let children has one chance to catch the touch, and request the swipe not intercept
929
-                    //useful when swipeLayout wrap a swipeLayout or other gestural layout
930
-                    return false;
931
-                }
932
-                break;
933
-
934
-            case MotionEvent.ACTION_CANCEL:
935
-            case MotionEvent.ACTION_UP:
936
-                mIsBeingDragged = false;
937
-                mDragHelper.processTouchEvent(ev);
938
-                break;
939
-            default://handle other action, such as ACTION_POINTER_DOWN/UP
940
-                mDragHelper.processTouchEvent(ev);
941
-        }
942
-        return mIsBeingDragged;
943
-    }
944
-
945
-    private float sX = -1, sY = -1;
946
-
947
-    @Override
948
-    public boolean onTouchEvent(MotionEvent event) {
949
-        if (!isSwipeEnabled()) return super.onTouchEvent(event);
950
-
951
-        int action = event.getActionMasked();
952
-        gestureDetector.onTouchEvent(event);
953
-
954
-        switch (action) {
955
-            case MotionEvent.ACTION_DOWN:
956
-                mDragHelper.processTouchEvent(event);
957
-                sX = event.getRawX();
958
-                sY = event.getRawY();
959
-
960
-
961
-            case MotionEvent.ACTION_MOVE: {
962
-                //the drag state and the direction are already judged at onInterceptTouchEvent
963
-                checkCanDrag(event);
964
-                if (mIsBeingDragged) {
965
-                    getParent().requestDisallowInterceptTouchEvent(true);
966
-                    mDragHelper.processTouchEvent(event);
967
-                }
968
-                break;
969
-            }
970
-            case MotionEvent.ACTION_UP:
971
-            case MotionEvent.ACTION_CANCEL:
972
-                mIsBeingDragged = false;
973
-                mDragHelper.processTouchEvent(event);
974
-                break;
975
-
976
-            default://handle other action, such as ACTION_POINTER_DOWN/UP
977
-                mDragHelper.processTouchEvent(event);
978
-        }
979
-
980
-        return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN;
981
-    }
982
-
983
-    public boolean isClickToClose() {
984
-        return mClickToClose;
985
-    }
986
-
987
-    public void setClickToClose(boolean mClickToClose) {
988
-        this.mClickToClose = mClickToClose;
989
-    }
990
-
991
-    public void setSwipeEnabled(boolean enabled) {
992
-        mSwipeEnabled = enabled;
993
-    }
994
-
995
-    public boolean isSwipeEnabled() {
996
-        return mSwipeEnabled;
997
-    }
998
-
999
-    public boolean isLeftSwipeEnabled() {
1000
-        View bottomView = mDragEdges.get(DragEdge.Left);
1001
-        return bottomView != null && bottomView.getParent() == this
1002
-                && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Left.ordinal()];
1003
-    }
1004
-
1005
-    public void setLeftSwipeEnabled(boolean leftSwipeEnabled) {
1006
-        this.mSwipesEnabled[DragEdge.Left.ordinal()] = leftSwipeEnabled;
1007
-    }
1008
-
1009
-    public boolean isRightSwipeEnabled() {
1010
-        View bottomView = mDragEdges.get(DragEdge.Right);
1011
-        return bottomView != null && bottomView.getParent() == this
1012
-                && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Right.ordinal()];
1013
-    }
1014
-
1015
-    public void setRightSwipeEnabled(boolean rightSwipeEnabled) {
1016
-        this.mSwipesEnabled[DragEdge.Right.ordinal()] = rightSwipeEnabled;
1017
-    }
1018
-
1019
-    public boolean isTopSwipeEnabled() {
1020
-        View bottomView = mDragEdges.get(DragEdge.Top);
1021
-        return bottomView != null && bottomView.getParent() == this
1022
-                && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Top.ordinal()];
1023
-    }
1024
-
1025
-    public void setTopSwipeEnabled(boolean topSwipeEnabled) {
1026
-        this.mSwipesEnabled[DragEdge.Top.ordinal()] = topSwipeEnabled;
1027
-    }
1028
-
1029
-    public boolean isBottomSwipeEnabled() {
1030
-        View bottomView = mDragEdges.get(DragEdge.Bottom);
1031
-        return bottomView != null && bottomView.getParent() == this
1032
-                && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Bottom.ordinal()];
1033
-    }
1034
-
1035
-    public void setBottomSwipeEnabled(boolean bottomSwipeEnabled) {
1036
-        this.mSwipesEnabled[DragEdge.Bottom.ordinal()] = bottomSwipeEnabled;
1037
-    }
1038
-    /***
1039
-     * Returns the percentage of revealing at which the view below should the view finish opening
1040
-     * if it was already open before dragging
1041
-     * @returns  The percentage of view revealed to trigger, default value is 0.25
1042
-     */
1043
-    public float getWillOpenPercentAfterOpen() {
1044
-        return mWillOpenPercentAfterOpen;
1045
-    }
1046
-
1047
-    /***
1048
-     * Allows to stablish at what percentage of revealing the view below should the view finish opening
1049
-     * if it was already open before dragging
1050
-     * @param willOpenPercentAfterOpen The percentage of view revealed to trigger, default value is 0.25
1051
-     */
1052
-    public void setWillOpenPercentAfterOpen(float willOpenPercentAfterOpen) {
1053
-        this.mWillOpenPercentAfterOpen = willOpenPercentAfterOpen;
1054
-    }
1055
-    /***
1056
-     * Returns the percentage of revealing at which the view below should the view finish opening
1057
-     * if it was already closed before dragging
1058
-     * @returns  The percentage of view revealed to trigger, default value is 0.25
1059
-     */
1060
-    public float getWillOpenPercentAfterClose() {
1061
-        return mWillOpenPercentAfterClose;
1062
-    }
1063
-    /***
1064
-     * Allows to stablish at what percentage of revealing the view below should the view finish opening
1065
-     * if it was already closed before dragging
1066
-     * @param willOpenPercentAfterClose The percentage of view revealed to trigger, default value is 0.75
1067
-     */
1068
-    public void setWillOpenPercentAfterClose(float willOpenPercentAfterClose) {
1069
-        this.mWillOpenPercentAfterClose = willOpenPercentAfterClose;
1070
-    }
1071
-
1072
-    private boolean insideAdapterView() {
1073
-        return getAdapterView() != null;
1074
-    }
1075
-
1076
-    private AdapterView getAdapterView() {
1077
-        ViewParent t = getParent();
1078
-        if (t instanceof AdapterView) {
1079
-            return (AdapterView) t;
1080
-        }
1081
-        return null;
1082
-    }
1083
-
1084
-    private void performAdapterViewItemClick() {
1085
-        if (getOpenStatus() != Status.Close) return;
1086
-        ViewParent t = getParent();
1087
-        if (t instanceof AdapterView) {
1088
-            AdapterView view = (AdapterView) t;
1089
-            int p = view.getPositionForView(SwipeLayout.this);
1090
-            if (p != AdapterView.INVALID_POSITION) {
1091
-                view.performItemClick(view.getChildAt(p - view.getFirstVisiblePosition()), p, view
1092
-                        .getAdapter().getItemId(p));
1093
-            }
1094
-        }
1095
-    }
1096
-
1097
-    private boolean performAdapterViewItemLongClick() {
1098
-        if (getOpenStatus() != Status.Close) return false;
1099
-        ViewParent t = getParent();
1100
-        if (t instanceof AdapterView) {
1101
-            AdapterView view = (AdapterView) t;
1102
-            int p = view.getPositionForView(SwipeLayout.this);
1103
-            if (p == AdapterView.INVALID_POSITION) return false;
1104
-            long vId = view.getItemIdAtPosition(p);
1105
-            boolean handled = false;
1106
-            try {
1107
-                Method m = AbsListView.class.getDeclaredMethod("performLongPress", View.class, int.class, long.class);
1108
-                m.setAccessible(true);
1109
-                handled = (boolean) m.invoke(view, SwipeLayout.this, p, vId);
1110
-
1111
-            } catch (Exception e) {
1112
-                e.printStackTrace();
1113
-
1114
-                if (view.getOnItemLongClickListener() != null) {
1115
-                    handled = view.getOnItemLongClickListener().onItemLongClick(view, SwipeLayout.this, p, vId);
1116
-                }
1117
-                if (handled) {
1118
-                    view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
1119
-                }
1120
-            }
1121
-            return handled;
1122
-        }
1123
-        return false;
1124
-    }
1125
-
1126
-    @Override
1127
-    protected void onAttachedToWindow() {
1128
-        super.onAttachedToWindow();
1129
-        if (insideAdapterView()) {
1130
-            if (clickListener == null) {
1131
-                setOnClickListener(new OnClickListener() {
1132
-                    @Override
1133
-                    public void onClick(View v) {
1134
-                        performAdapterViewItemClick();
1135
-                    }
1136
-                });
1137
-            }
1138
-            if (longClickListener == null) {
1139
-                setOnLongClickListener(new OnLongClickListener() {
1140
-                    @Override
1141
-                    public boolean onLongClick(View v) {
1142
-                        performAdapterViewItemLongClick();
1143
-                        return true;
1144
-                    }
1145
-                });
1146
-            }
1147
-        }
1148
-    }
1149
-
1150
-    OnClickListener clickListener;
1151
-
1152
-    @Override
1153
-    public void setOnClickListener(OnClickListener l) {
1154
-        super.setOnClickListener(l);
1155
-        clickListener = l;
1156
-    }
1157
-
1158
-    OnLongClickListener longClickListener;
1159
-
1160
-    @Override
1161
-    public void setOnLongClickListener(OnLongClickListener l) {
1162
-        super.setOnLongClickListener(l);
1163
-        longClickListener = l;
1164
-    }
1165
-
1166
-    private Rect hitSurfaceRect;
1167
-
1168
-    private boolean isTouchOnSurface(MotionEvent ev) {
1169
-        View surfaceView = getSurfaceView();
1170
-        if (surfaceView == null) {
1171
-            return false;
1172
-        }
1173
-        if (hitSurfaceRect == null) {
1174
-            hitSurfaceRect = new Rect();
1175
-        }
1176
-        surfaceView.getHitRect(hitSurfaceRect);
1177
-        return hitSurfaceRect.contains((int) ev.getX(), (int) ev.getY());
1178
-    }
1179
-
1180
-    private GestureDetector gestureDetector = new GestureDetector(getContext(), new SwipeDetector());
1181
-
1182
-    class SwipeDetector extends GestureDetector.SimpleOnGestureListener {
1183
-        @Override
1184
-        public boolean onSingleTapUp(MotionEvent e) {
1185
-            if (mClickToClose && isTouchOnSurface(e)) {
1186
-                close();
1187
-            }
1188
-            return super.onSingleTapUp(e);
1189
-        }
1190
-
1191
-        @Override
1192
-        public boolean onDoubleTap(MotionEvent e) {
1193
-            if (mDoubleClickListener != null) {
1194
-                View target;
1195
-                View bottom = getCurrentBottomView();
1196
-                View surface = getSurfaceView();
1197
-                if (bottom != null && e.getX() > bottom.getLeft() && e.getX() < bottom.getRight()
1198
-                        && e.getY() > bottom.getTop() && e.getY() < bottom.getBottom()) {
1199
-                    target = bottom;
1200
-                } else {
1201
-                    target = surface;
1202
-                }
1203
-                mDoubleClickListener.onDoubleClick(SwipeLayout.this, target == surface);
1204
-            }
1205
-            return true;
1206
-        }
1207
-    }
1208
-
1209
-    /**
1210
-     * set the drag distance, it will force set the bottom view's width or
1211
-     * height via this value.
1212
-     *
1213
-     * @param max max distance in dp unit
1214
-     */
1215
-    public void setDragDistance(int max) {
1216
-        if (max < 0) max = 0;
1217
-        mDragDistance = dp2px(max);
1218
-        requestLayout();
1219
-    }
1220
-
1221
-    /**
1222
-     * There are 2 diffirent show mode.
1223
-     *
1224
-     * @param mode
1225
-     */
1226
-    public void setShowMode(ShowMode mode) {
1227
-        mShowMode = mode;
1228
-        requestLayout();
1229
-    }
1230
-
1231
-    public DragEdge getDragEdge() {
1232
-        return mCurrentDragEdge;
1233
-    }
1234
-
1235
-    public int getDragDistance() {
1236
-        return mDragDistance;
1237
-    }
1238
-
1239
-    public ShowMode getShowMode() {
1240
-        return mShowMode;
1241
-    }
1242
-
1243
-    /**
1244
-     * return null if there is no surface view(no children)
1245
-     */
1246
-    public View getSurfaceView() {
1247
-        if (getChildCount() == 0) return null;
1248
-        return getChildAt(getChildCount() - 1);
1249
-    }
1250
-
1251
-    /**
1252
-     * return null if there is no bottom view
1253
-     */
1254
-    @Nullable
1255
-    public View getCurrentBottomView() {
1256
-        List<View> bottoms = getBottomViews();
1257
-        if (mCurrentDragEdge.ordinal() < bottoms.size()) {
1258
-            return bottoms.get(mCurrentDragEdge.ordinal());
1259
-        }
1260
-        return null;
1261
-    }
1262
-
1263
-    /**
1264
-     * @return all bottomViews: left, top, right, bottom (may null if the edge is not set)
1265
-     */
1266
-    public List<View> getBottomViews() {
1267
-        ArrayList<View> bottoms = new ArrayList<View>();
1268
-        for (DragEdge dragEdge : DragEdge.values()) {
1269
-            bottoms.add(mDragEdges.get(dragEdge));
1270
-        }
1271
-        return bottoms;
1272
-    }
1273
-
1274
-    public enum Status {
1275
-        Middle,
1276
-        Open,
1277
-        Close
1278
-    }
1279
-
1280
-    /**
1281
-     * get the open status.
1282
-     *
1283
-     */
1284
-    public Status getOpenStatus() {
1285
-        View surfaceView = getSurfaceView();
1286
-        if (surfaceView == null) {
1287
-            return Status.Close;
1288
-        }
1289
-        int surfaceLeft = surfaceView.getLeft();
1290
-        int surfaceTop = surfaceView.getTop();
1291
-        if (surfaceLeft == getPaddingLeft() && surfaceTop == getPaddingTop()) return Status.Close;
1292
-
1293
-        if (surfaceLeft == (getPaddingLeft() - mDragDistance) || surfaceLeft == (getPaddingLeft() + mDragDistance)
1294
-                || surfaceTop == (getPaddingTop() - mDragDistance) || surfaceTop == (getPaddingTop() + mDragDistance))
1295
-            return Status.Open;
1296
-
1297
-        return Status.Middle;
1298
-    }
1299
-
1300
-
1301
-    /**
1302
-     * Process the surface release event.
1303
-     *
1304
-     * @param xvel                 xVelocity
1305
-     * @param yvel                 yVelocity
1306
-     * @param isCloseBeforeDragged the open state before drag
1307
-     */
1308
-    protected void processHandRelease(float xvel, float yvel, boolean isCloseBeforeDragged) {
1309
-        float minVelocity = mDragHelper.getMinVelocity();
1310
-        View surfaceView = getSurfaceView();
1311
-        DragEdge currentDragEdge = mCurrentDragEdge;
1312
-        if (currentDragEdge == null || surfaceView == null) {
1313
-            return;
1314
-        }
1315
-        float willOpenPercent = (isCloseBeforeDragged ? mWillOpenPercentAfterClose : mWillOpenPercentAfterOpen);
1316
-        if (currentDragEdge == DragEdge.Left) {
1317
-            if (xvel > minVelocity) open();
1318
-            else if (xvel < -minVelocity) close();
1319
-            else {
1320
-                float openPercent = 1f * getSurfaceView().getLeft() / mDragDistance;
1321
-                if (openPercent > willOpenPercent) open();
1322
-                else close();
1323
-            }
1324
-        } else if (currentDragEdge == DragEdge.Right) {
1325
-            if (xvel > minVelocity) close();
1326
-            else if (xvel < -minVelocity) open();
1327
-            else {
1328
-                float openPercent = 1f * (-getSurfaceView().getLeft()) / mDragDistance;
1329
-                if (openPercent > willOpenPercent) open();
1330
-                else close();
1331
-            }
1332
-        } else if (currentDragEdge == DragEdge.Top) {
1333
-            if (yvel > minVelocity) open();
1334
-            else if (yvel < -minVelocity) close();
1335
-            else {
1336
-                float openPercent = 1f * getSurfaceView().getTop() / mDragDistance;
1337
-                if (openPercent > willOpenPercent) open();
1338
-                else close();
1339
-            }
1340
-        } else if (currentDragEdge == DragEdge.Bottom) {
1341
-            if (yvel > minVelocity) close();
1342
-            else if (yvel < -minVelocity) open();
1343
-            else {
1344
-                float openPercent = 1f * (-getSurfaceView().getTop()) / mDragDistance;
1345
-                if (openPercent > willOpenPercent) open();
1346
-                else close();
1347
-            }
1348
-        }
1349
-    }
1350
-
1351
-    /**
1352
-     * smoothly open surface.
1353
-     */
1354
-    public void open() {
1355
-        open(true, true);
1356
-    }
1357
-
1358
-    public void open(boolean smooth) {
1359
-        open(smooth, true);
1360
-    }
1361
-
1362
-    public void open(boolean smooth, boolean notify) {
1363
-        View surface = getSurfaceView(), bottom = getCurrentBottomView();
1364
-        if (surface == null) {
1365
-            return;
1366
-        }
1367
-        int dx, dy;
1368
-        Rect rect = computeSurfaceLayoutArea(true);
1369
-        if (smooth) {
1370
-            mDragHelper.smoothSlideViewTo(surface, rect.left, rect.top);
1371
-        } else {
1372
-            dx = rect.left - surface.getLeft();
1373
-            dy = rect.top - surface.getTop();
1374
-            surface.layout(rect.left, rect.top, rect.right, rect.bottom);
1375
-            if (getShowMode() == ShowMode.PullOut) {
1376
-                Rect bRect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, rect);
1377
-                if (bottom != null) {
1378
-                    bottom.layout(bRect.left, bRect.top, bRect.right, bRect.bottom);
1379
-                }
1380
-            }
1381
-            if (notify) {
1382
-                dispatchRevealEvent(rect.left, rect.top, rect.right, rect.bottom);
1383
-                dispatchSwipeEvent(rect.left, rect.top, dx, dy);
1384
-            } else {
1385
-                safeBottomView();
1386
-            }
1387
-        }
1388
-        invalidate();
1389
-    }
1390
-
1391
-    public void open(DragEdge edge) {
1392
-        setCurrentDragEdge(edge);
1393
-        open(true, true);
1394
-    }
1395
-
1396
-    public void open(boolean smooth, DragEdge edge) {
1397
-        setCurrentDragEdge(edge);
1398
-        open(smooth, true);
1399
-    }
1400
-
1401
-    public void open(boolean smooth, boolean notify, DragEdge edge) {
1402
-        setCurrentDragEdge(edge);
1403
-        open(smooth, notify);
1404
-    }
1405
-
1406
-    /**
1407
-     * smoothly close surface.
1408
-     */
1409
-    public void close() {
1410
-        close(true, true);
1411
-    }
1412
-
1413
-    public void close(boolean smooth) {
1414
-        close(smooth, true);
1415
-    }
1416
-
1417
-    /**
1418
-     * close surface
1419
-     *
1420
-     * @param smooth smoothly or not.
1421
-     * @param notify if notify all the listeners.
1422
-     */
1423
-    public void close(boolean smooth, boolean notify) {
1424
-        View surface = getSurfaceView();
1425
-        if (surface == null) {
1426
-            return;
1427
-        }
1428
-        int dx, dy;
1429
-        if (smooth)
1430
-            mDragHelper.smoothSlideViewTo(getSurfaceView(), getPaddingLeft(), getPaddingTop());
1431
-        else {
1432
-            Rect rect = computeSurfaceLayoutArea(false);
1433
-            dx = rect.left - surface.getLeft();
1434
-            dy = rect.top - surface.getTop();
1435
-            surface.layout(rect.left, rect.top, rect.right, rect.bottom);
1436
-            if (notify) {
1437
-                dispatchRevealEvent(rect.left, rect.top, rect.right, rect.bottom);
1438
-                dispatchSwipeEvent(rect.left, rect.top, dx, dy);
1439
-            } else {
1440
-                safeBottomView();
1441
-            }
1442
-        }
1443
-        invalidate();
1444
-    }
1445
-
1446
-    public void toggle() {
1447
-        toggle(true);
1448
-    }
1449
-
1450
-    public void toggle(boolean smooth) {
1451
-        if (getOpenStatus() == Status.Open)
1452
-            close(smooth);
1453
-        else if (getOpenStatus() == Status.Close) open(smooth);
1454
-    }
1455
-
1456
-
1457
-    /**
1458
-     * a helper function to compute the Rect area that surface will hold in.
1459
-     *
1460
-     * @param open open status or close status.
1461
-     */
1462
-    private Rect computeSurfaceLayoutArea(boolean open) {
1463
-        int l = getPaddingLeft(), t = getPaddingTop();
1464
-        if (open) {
1465
-            if (mCurrentDragEdge == DragEdge.Left)
1466
-                l = getPaddingLeft() + mDragDistance;
1467
-            else if (mCurrentDragEdge == DragEdge.Right)
1468
-                l = getPaddingLeft() - mDragDistance;
1469
-            else if (mCurrentDragEdge == DragEdge.Top)
1470
-                t = getPaddingTop() + mDragDistance;
1471
-            else t = getPaddingTop() - mDragDistance;
1472
-        }
1473
-        return new Rect(l, t, l + getMeasuredWidth(), t + getMeasuredHeight());
1474
-    }
1475
-
1476
-    private Rect computeBottomLayoutAreaViaSurface(ShowMode mode, Rect surfaceArea) {
1477
-        Rect rect = surfaceArea;
1478
-        View bottomView = getCurrentBottomView();
1479
-
1480
-        int bl = rect.left, bt = rect.top, br = rect.right, bb = rect.bottom;
1481
-        if (mode == ShowMode.PullOut) {
1482
-            if (mCurrentDragEdge == DragEdge.Left)
1483
-                bl = rect.left - mDragDistance;
1484
-            else if (mCurrentDragEdge == DragEdge.Right)
1485
-                bl = rect.right;
1486
-            else if (mCurrentDragEdge == DragEdge.Top)
1487
-                bt = rect.top - mDragDistance;
1488
-            else bt = rect.bottom;
1489
-
1490
-            if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) {
1491
-                bb = rect.bottom;
1492
-                br = bl + (bottomView == null ? 0 : bottomView.getMeasuredWidth());
1493
-            } else {
1494
-                bb = bt + (bottomView == null ? 0 : bottomView.getMeasuredHeight());
1495
-                br = rect.right;
1496
-            }
1497
-        } else if (mode == ShowMode.LayDown) {
1498
-            if (mCurrentDragEdge == DragEdge.Left)
1499
-                br = bl + mDragDistance;
1500
-            else if (mCurrentDragEdge == DragEdge.Right)
1501
-                bl = br - mDragDistance;
1502
-            else if (mCurrentDragEdge == DragEdge.Top)
1503
-                bb = bt + mDragDistance;
1504
-            else bt = bb - mDragDistance;
1505
-
1506
-        }
1507
-        return new Rect(bl, bt, br, bb);
1508
-
1509
-    }
1510
-
1511
-    private Rect computeBottomLayDown(DragEdge dragEdge) {
1512
-        int bl = getPaddingLeft(), bt = getPaddingTop();
1513
-        int br, bb;
1514
-        if (dragEdge == DragEdge.Right) {
1515
-            bl = getMeasuredWidth() - mDragDistance;
1516
-        } else if (dragEdge == DragEdge.Bottom) {
1517
-            bt = getMeasuredHeight() - mDragDistance;
1518
-        }
1519
-        if (dragEdge == DragEdge.Left || dragEdge == DragEdge.Right) {
1520
-            br = bl + mDragDistance;
1521
-            bb = bt + getMeasuredHeight();
1522
-        } else {
1523
-            br = bl + getMeasuredWidth();
1524
-            bb = bt + mDragDistance;
1525
-        }
1526
-        return new Rect(bl, bt, br, bb);
1527
-    }
1528
-
1529
-    public void setOnDoubleClickListener(DoubleClickListener doubleClickListener) {
1530
-        mDoubleClickListener = doubleClickListener;
1531
-    }
1532
-
1533
-    public interface DoubleClickListener {
1534
-        void onDoubleClick(SwipeLayout layout, boolean surface);
1535
-    }
1536
-
1537
-    private int dp2px(float dp) {
1538
-        return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f);
1539
-    }
1540
-
1541
-
1542
-    /**
1543
-     * Deprecated, use {@link #setDrag(DragEdge, View)}
1544
-     */
1545
-    @Deprecated
1546
-    public void setDragEdge(DragEdge dragEdge) {
1547
-        clearDragEdge();
1548
-        if (getChildCount() >= 2) {
1549
-            mDragEdges.put(dragEdge, getChildAt(getChildCount() - 2));
1550
-        }
1551
-        setCurrentDragEdge(dragEdge);
1552
-    }
1553
-
1554
-    public void onViewRemoved(View child) {
1555
-        for (Map.Entry<DragEdge, View> entry : new HashMap<DragEdge, View>(mDragEdges).entrySet()) {
1556
-            if (entry.getValue() == child) {
1557
-                mDragEdges.remove(entry.getKey());
1558
-            }
1559
-        }
1560
-    }
1561
-
1562
-    public Map<DragEdge, View> getDragEdgeMap() {
1563
-        return mDragEdges;
1564
-    }
1565
-
1566
-    /**
1567
-     * Deprecated, use {@link #getDragEdgeMap()}
1568
-     */
1569
-    @Deprecated
1570
-    public List<DragEdge> getDragEdges() {
1571
-        return new ArrayList<DragEdge>(mDragEdges.keySet());
1572
-    }
1573
-
1574
-    /**
1575
-     * Deprecated, use {@link #setDrag(DragEdge, View)}
1576
-     */
1577
-    @Deprecated
1578
-    public void setDragEdges(List<DragEdge> dragEdges) {
1579
-        clearDragEdge();
1580
-        for (int i = 0, size = Math.min(dragEdges.size(), getChildCount() - 1); i < size; i++) {
1581
-            DragEdge dragEdge = dragEdges.get(i);
1582
-            mDragEdges.put(dragEdge, getChildAt(i));
1583
-        }
1584
-        if (dragEdges.size() == 0 || dragEdges.contains(DefaultDragEdge)) {
1585
-            setCurrentDragEdge(DefaultDragEdge);
1586
-        } else {
1587
-            setCurrentDragEdge(dragEdges.get(0));
1588
-        }
1589
-    }
1590
-
1591
-    /**
1592
-     * Deprecated, use {@link #addDrag(DragEdge, View)}
1593
-     */
1594
-    @Deprecated
1595
-    public void setDragEdges(DragEdge... mDragEdges) {
1596
-        clearDragEdge();
1597
-        setDragEdges(Arrays.asList(mDragEdges));
1598
-    }
1599
-
1600
-    /**
1601
-     * Deprecated, use {@link #addDrag(DragEdge, View)}
1602
-     * When using multiple drag edges it's a good idea to pass the ids of the views that
1603
-     * you're using for the left, right, top bottom views (-1 if you're not using a particular view)
1604
-     */
1605
-    @Deprecated
1606
-    public void setBottomViewIds(int leftId, int rightId, int topId, int bottomId) {
1607
-        addDrag(DragEdge.Left, findViewById(leftId));
1608
-        addDrag(DragEdge.Right, findViewById(rightId));
1609
-        addDrag(DragEdge.Top, findViewById(topId));
1610
-        addDrag(DragEdge.Bottom, findViewById(bottomId));
1611
-    }
1612
-
1613
-    private float getCurrentOffset() {
1614
-        if (mCurrentDragEdge == null) return 0;
1615
-        return mEdgeSwipesOffset[mCurrentDragEdge.ordinal()];
1616
-    }
1617
-
1618
-    private void setCurrentDragEdge(DragEdge dragEdge) {
1619
-        mCurrentDragEdge = dragEdge;
1620
-        updateBottomViews();
1621
-    }
1622
-
1623
-    private void updateBottomViews() {
1624
-        View currentBottomView = getCurrentBottomView();
1625
-        if (currentBottomView != null) {
1626
-            if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) {
1627
-                mDragDistance = currentBottomView.getMeasuredWidth() - dp2px(getCurrentOffset());
1628
-            } else {
1629
-                mDragDistance = currentBottomView.getMeasuredHeight() - dp2px(getCurrentOffset());
1630
-            }
1631
-        }
1632
-
1633
-        if (mShowMode == ShowMode.PullOut) {
1634
-            layoutPullOut();
1635
-        } else if (mShowMode == ShowMode.LayDown) {
1636
-            layoutLayDown();
1637
-        }
1638
-
1639
-        safeBottomView();
1640
-    }
1641
-}

+ 19 - 0
views/src/main/java/com/android/views/swipebacklayout/SwipeBackActivityBase.java

@@ -0,0 +1,19 @@
1
+package com.android.views.swipebacklayout;
2
+
3
+/**
4
+ * @author Yrom
5
+ */
6
+public interface SwipeBackActivityBase {
7
+    /**
8
+     * @return the SwipeBackLayout associated with this activity.
9
+     */
10
+    public abstract SwipeBackLayout getSwipeBackLayout();
11
+
12
+    public abstract void setSwipeBackEnable(boolean enable);
13
+
14
+    /**
15
+     * Scroll out contentView and finish the activity
16
+     */
17
+    public abstract void scrollToFinishActivity();
18
+
19
+}

+ 60 - 0
views/src/main/java/com/android/views/swipebacklayout/SwipeBackActivityHelper.java

@@ -0,0 +1,60 @@
1
+package com.android.views.swipebacklayout;
2
+
3
+import android.app.Activity;
4
+import android.graphics.Color;
5
+import android.graphics.drawable.ColorDrawable;
6
+import android.view.LayoutInflater;
7
+import android.view.View;
8
+
9
+import com.android.views.R;
10
+
11
+/**
12
+ * @author Yrom
13
+ */
14
+public class SwipeBackActivityHelper {
15
+    private Activity mActivity;
16
+
17
+    private SwipeBackLayout mSwipeBackLayout;
18
+
19
+    public SwipeBackActivityHelper(Activity activity) {
20
+        mActivity = activity;
21
+    }
22
+
23
+    @SuppressWarnings("deprecation")
24
+    public void onActivityCreate() {
25
+        mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
26
+        mActivity.getWindow().getDecorView().setBackgroundDrawable(null);
27
+        mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(
28
+                R.layout.swipeback_layout, null);
29
+        mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() {
30
+            @Override
31
+            public void onScrollStateChange(int state, float scrollPercent) {
32
+            }
33
+
34
+            @Override
35
+            public void onEdgeTouch(int edgeFlag) {
36
+                Utils.convertActivityToTranslucent(mActivity);
37
+            }
38
+
39
+            @Override
40
+            public void onScrollOverThreshold() {
41
+
42
+            }
43
+        });
44
+    }
45
+
46
+    public void onPostCreate() {
47
+        mSwipeBackLayout.attachToActivity(mActivity);
48
+    }
49
+
50
+    public View findViewById(int id) {
51
+        if (mSwipeBackLayout != null) {
52
+            return mSwipeBackLayout.findViewById(id);
53
+        }
54
+        return null;
55
+    }
56
+
57
+    public SwipeBackLayout getSwipeBackLayout() {
58
+        return mSwipeBackLayout;
59
+    }
60
+}

+ 606 - 0
views/src/main/java/com/android/views/swipebacklayout/SwipeBackLayout.java

@@ -0,0 +1,606 @@
1
+package com.android.views.swipebacklayout;
2
+
3
+import android.app.Activity;
4
+import android.content.Context;
5
+import android.content.res.TypedArray;
6
+import android.graphics.Canvas;
7
+import android.graphics.Rect;
8
+import android.graphics.drawable.Drawable;
9
+import android.support.v4.view.ViewCompat;
10
+import android.util.AttributeSet;
11
+import android.view.MotionEvent;
12
+import android.view.View;
13
+import android.view.ViewGroup;
14
+import android.widget.FrameLayout;
15
+
16
+import com.android.views.R;
17
+
18
+import java.util.ArrayList;
19
+import java.util.List;
20
+
21
+
22
+public class SwipeBackLayout extends FrameLayout {
23
+    /**
24
+     * Minimum velocity that will be detected as a fling
25
+     */
26
+    private static final int MIN_FLING_VELOCITY = 400; // dips per second
27
+
28
+    private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
29
+
30
+    private static final int FULL_ALPHA = 255;
31
+
32
+    /**
33
+     * Edge flag indicating that the left edge should be affected.
34
+     */
35
+    public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT;
36
+
37
+    /**
38
+     * Edge flag indicating that the right edge should be affected.
39
+     */
40
+    public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT;
41
+
42
+    /**
43
+     * Edge flag indicating that the bottom edge should be affected.
44
+     */
45
+    public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM;
46
+
47
+    /**
48
+     * Edge flag set indicating all edges should be affected.
49
+     */
50
+    public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM;
51
+
52
+    /**
53
+     * A view is not currently being dragged or animating as a result of a
54
+     * fling/snap.
55
+     */
56
+    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
57
+
58
+    /**
59
+     * A view is currently being dragged. The position is currently changing as
60
+     * a result of user input or simulated user input.
61
+     */
62
+    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
63
+
64
+    /**
65
+     * A view is currently settling into place as a result of a fling or
66
+     * predefined non-interactive motion.
67
+     */
68
+    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
69
+
70
+    /**
71
+     * Default threshold of scroll
72
+     */
73
+    private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f;
74
+
75
+    private static final int OVERSCROLL_DISTANCE = 10;
76
+
77
+    private static final int[] EDGE_FLAGS = {
78
+            EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL
79
+    };
80
+
81
+    private int mEdgeFlag;
82
+
83
+    /**
84
+     * Threshold of scroll, we will close the activity, when scrollPercent over
85
+     * this value;
86
+     */
87
+    private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD;
88
+
89
+    private Activity mActivity;
90
+
91
+    private boolean mEnable = true;
92
+
93
+    private View mContentView;
94
+
95
+    private ViewDragHelper mDragHelper;
96
+
97
+    private float mScrollPercent;
98
+
99
+    private int mContentLeft;
100
+
101
+    private int mContentTop;
102
+
103
+    /**
104
+     * The set of listeners to be sent events through.
105
+     */
106
+    private List<SwipeListener> mListeners;
107
+
108
+    private Drawable mShadowLeft;
109
+
110
+    private Drawable mShadowRight;
111
+
112
+    private Drawable mShadowBottom;
113
+
114
+    private float mScrimOpacity;
115
+
116
+    private int mScrimColor = DEFAULT_SCRIM_COLOR;
117
+
118
+    private boolean mInLayout;
119
+
120
+    private Rect mTmpRect = new Rect();
121
+
122
+    /**
123
+     * Edge being dragged
124
+     */
125
+    private int mTrackingEdge;
126
+
127
+    public SwipeBackLayout(Context context) {
128
+        this(context, null);
129
+    }
130
+
131
+    public SwipeBackLayout(Context context, AttributeSet attrs) {
132
+        this(context, attrs, R.attr.SwipeBackLayoutStyle);
133
+    }
134
+
135
+    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
136
+        super(context, attrs);
137
+        mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());
138
+
139
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle,
140
+                R.style.SwipeBackLayout);
141
+
142
+        int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1);
143
+        if (edgeSize > 0)
144
+            setEdgeSize(edgeSize);
145
+        int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)];
146
+        setEdgeTrackingEnabled(mode);
147
+
148
+        int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left,
149
+                R.drawable.shadow_left);
150
+        int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right,
151
+                R.drawable.shadow_right);
152
+        int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom,
153
+                R.drawable.shadow_bottom);
154
+        setShadow(shadowLeft, EDGE_LEFT);
155
+        setShadow(shadowRight, EDGE_RIGHT);
156
+        setShadow(shadowBottom, EDGE_BOTTOM);
157
+        a.recycle();
158
+        final float density = getResources().getDisplayMetrics().density;
159
+        final float minVel = MIN_FLING_VELOCITY * density;
160
+        mDragHelper.setMinVelocity(minVel);
161
+        mDragHelper.setMaxVelocity(minVel * 2f);
162
+    }
163
+
164
+    /**
165
+     * Sets the sensitivity of the NavigationLayout.
166
+     *
167
+     * @param context     The application context.
168
+     * @param sensitivity value between 0 and 1, the final value for touchSlop =
169
+     *                    ViewConfiguration.getScaledTouchSlop * (1 / s);
170
+     */
171
+    public void setSensitivity(Context context, float sensitivity) {
172
+        mDragHelper.setSensitivity(context, sensitivity);
173
+    }
174
+
175
+    /**
176
+     * Set up contentView which will be moved by user gesture
177
+     *
178
+     * @param view
179
+     */
180
+    private void setContentView(View view) {
181
+        mContentView = view;
182
+    }
183
+
184
+    public void setEnableGesture(boolean enable) {
185
+        mEnable = enable;
186
+    }
187
+
188
+    /**
189
+     * Enable edge tracking for the selected edges of the parent view. The
190
+     * callback's
191
+     *
192
+     * @param edgeFlags Combination of edge flags describing the edges to watch
193
+     * @see #EDGE_LEFT
194
+     * @see #EDGE_RIGHT
195
+     * @see #EDGE_BOTTOM
196
+     */
197
+    public void setEdgeTrackingEnabled(int edgeFlags) {
198
+        mEdgeFlag = edgeFlags;
199
+        mDragHelper.setEdgeTrackingEnabled(mEdgeFlag);
200
+    }
201
+
202
+    /**
203
+     * Set a color to use for the scrim that obscures primary content while a
204
+     * drawer is open.
205
+     *
206
+     * @param color Color to use in 0xAARRGGBB format.
207
+     */
208
+    public void setScrimColor(int color) {
209
+        mScrimColor = color;
210
+        invalidate();
211
+    }
212
+
213
+    /**
214
+     * Set the size of an edge. This is the range in pixels along the edges of
215
+     * this view that will actively detect edge touches or drags if edge
216
+     * tracking is enabled.
217
+     *
218
+     * @param size The size of an edge in pixels
219
+     */
220
+    public void setEdgeSize(int size) {
221
+        mDragHelper.setEdgeSize(size);
222
+    }
223
+
224
+    /**
225
+     * Register a callback to be invoked when a swipe event is sent to this
226
+     * view.
227
+     *
228
+     * @param listener the swipe listener to attach to this view
229
+     * @deprecated use {@link #addSwipeListener} instead
230
+     */
231
+    @Deprecated
232
+    public void setSwipeListener(SwipeListener listener) {
233
+        addSwipeListener(listener);
234
+    }
235
+
236
+    /**
237
+     * Add a callback to be invoked when a swipe event is sent to this view.
238
+     *
239
+     * @param listener the swipe listener to attach to this view
240
+     */
241
+    public void addSwipeListener(SwipeListener listener) {
242
+        if (mListeners == null) {
243
+            mListeners = new ArrayList<SwipeListener>();
244
+        }
245
+        mListeners.add(listener);
246
+    }
247
+
248
+    /**
249
+     * Removes a listener from the set of listeners
250
+     *
251
+     * @param listener
252
+     */
253
+    public void removeSwipeListener(SwipeListener listener) {
254
+        if (mListeners == null) {
255
+            return;
256
+        }
257
+        mListeners.remove(listener);
258
+    }
259
+
260
+    public static interface SwipeListener {
261
+        /**
262
+         * Invoke when state change
263
+         *
264
+         * @param state         flag to describe scroll state
265
+         * @param scrollPercent scroll percent of this view
266
+         * @see #STATE_IDLE
267
+         * @see #STATE_DRAGGING
268
+         * @see #STATE_SETTLING
269
+         */
270
+        public void onScrollStateChange(int state, float scrollPercent);
271
+
272
+        /**
273
+         * Invoke when edge touched
274
+         *
275
+         * @param edgeFlag edge flag describing the edge being touched
276
+         * @see #EDGE_LEFT
277
+         * @see #EDGE_RIGHT
278
+         * @see #EDGE_BOTTOM
279
+         */
280
+        public void onEdgeTouch(int edgeFlag);
281
+
282
+        /**
283
+         * Invoke when scroll percent over the threshold for the first time
284
+         */
285
+        public void onScrollOverThreshold();
286
+    }
287
+
288
+    /**
289
+     * Set scroll threshold, we will close the activity, when scrollPercent over
290
+     * this value
291
+     *
292
+     * @param threshold
293
+     */
294
+    public void setScrollThresHold(float threshold) {
295
+        if (threshold >= 1.0f || threshold <= 0) {
296
+            throw new IllegalArgumentException("Threshold value should be between 0 and 1.0");
297
+        }
298
+        mScrollThreshold = threshold;
299
+    }
300
+
301
+    /**
302
+     * Set a drawable used for edge shadow.
303
+     *
304
+     * @param shadow    Drawable to use
305
+     * @param edgeFlag Combination of edge flags describing the edge to set
306
+     * @see #EDGE_LEFT
307
+     * @see #EDGE_RIGHT
308
+     * @see #EDGE_BOTTOM
309
+     */
310
+    public void setShadow(Drawable shadow, int edgeFlag) {
311
+        if ((edgeFlag & EDGE_LEFT) != 0) {
312
+            mShadowLeft = shadow;
313
+        } else if ((edgeFlag & EDGE_RIGHT) != 0) {
314
+            mShadowRight = shadow;
315
+        } else if ((edgeFlag & EDGE_BOTTOM) != 0) {
316
+            mShadowBottom = shadow;
317
+        }
318
+        invalidate();
319
+    }
320
+
321
+    /**
322
+     * Set a drawable used for edge shadow.
323
+     *
324
+     * @param resId     Resource of drawable to use
325
+     * @param edgeFlag Combination of edge flags describing the edge to set
326
+     * @see #EDGE_LEFT
327
+     * @see #EDGE_RIGHT
328
+     * @see #EDGE_BOTTOM
329
+     */
330
+    public void setShadow(int resId, int edgeFlag) {
331
+        setShadow(getResources().getDrawable(resId), edgeFlag);
332
+    }
333
+
334
+    /**
335
+     * Scroll out contentView and finish the activity
336
+     */
337
+    public void scrollToFinishActivity() {
338
+        final int childWidth = mContentView.getWidth();
339
+        final int childHeight = mContentView.getHeight();
340
+
341
+        int left = 0, top = 0;
342
+        if ((mEdgeFlag & EDGE_LEFT) != 0) {
343
+            left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE;
344
+            mTrackingEdge = EDGE_LEFT;
345
+        } else if ((mEdgeFlag & EDGE_RIGHT) != 0) {
346
+            left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE;
347
+            mTrackingEdge = EDGE_RIGHT;
348
+        } else if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
349
+            top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE;
350
+            mTrackingEdge = EDGE_BOTTOM;
351
+        }
352
+
353
+        mDragHelper.smoothSlideViewTo(mContentView, left, top);
354
+        invalidate();
355
+    }
356
+
357
+    @Override
358
+    public boolean onInterceptTouchEvent(MotionEvent event) {
359
+        if (!mEnable) {
360
+            return false;
361
+        }
362
+        try {
363
+            return mDragHelper.shouldInterceptTouchEvent(event);
364
+        } catch (ArrayIndexOutOfBoundsException e) {
365
+            // FIXME: handle exception
366
+            // issues #9
367
+            return false;
368
+        }
369
+    }
370
+
371
+    @Override
372
+    public boolean onTouchEvent(MotionEvent event) {
373
+        if (!mEnable) {
374
+            return false;
375
+        }
376
+        mDragHelper.processTouchEvent(event);
377
+        return true;
378
+    }
379
+
380
+    @Override
381
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
382
+        mInLayout = true;
383
+        if (mContentView != null)
384
+            mContentView.layout(mContentLeft, mContentTop,
385
+                    mContentLeft + mContentView.getMeasuredWidth(),
386
+                    mContentTop + mContentView.getMeasuredHeight());
387
+        mInLayout = false;
388
+    }
389
+
390
+    @Override
391
+    public void requestLayout() {
392
+        if (!mInLayout) {
393
+            super.requestLayout();
394
+        }
395
+    }
396
+
397
+    @Override
398
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
399
+        final boolean drawContent = child == mContentView;
400
+
401
+        boolean ret = super.drawChild(canvas, child, drawingTime);
402
+        if (mScrimOpacity > 0 && drawContent
403
+                && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
404
+            drawShadow(canvas, child);
405
+            drawScrim(canvas, child);
406
+        }
407
+        return ret;
408
+    }
409
+
410
+    private void drawScrim(Canvas canvas, View child) {
411
+        final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
412
+        final int alpha = (int) (baseAlpha * mScrimOpacity);
413
+        final int color = alpha << 24 | (mScrimColor & 0xffffff);
414
+
415
+        if ((mTrackingEdge & EDGE_LEFT) != 0) {
416
+            canvas.clipRect(0, 0, child.getLeft(), getHeight());
417
+        } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
418
+            canvas.clipRect(child.getRight(), 0, getRight(), getHeight());
419
+        } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
420
+            canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight());
421
+        }
422
+        canvas.drawColor(color);
423
+    }
424
+
425
+    private void drawShadow(Canvas canvas, View child) {
426
+        final Rect childRect = mTmpRect;
427
+        child.getHitRect(childRect);
428
+
429
+        if ((mEdgeFlag & EDGE_LEFT) != 0) {
430
+            mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top,
431
+                    childRect.left, childRect.bottom);
432
+            mShadowLeft.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
433
+            mShadowLeft.draw(canvas);
434
+        }
435
+
436
+        if ((mEdgeFlag & EDGE_RIGHT) != 0) {
437
+            mShadowRight.setBounds(childRect.right, childRect.top,
438
+                    childRect.right + mShadowRight.getIntrinsicWidth(), childRect.bottom);
439
+            mShadowRight.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
440
+            mShadowRight.draw(canvas);
441
+        }
442
+
443
+        if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
444
+            mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right,
445
+                    childRect.bottom + mShadowBottom.getIntrinsicHeight());
446
+            mShadowBottom.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
447
+            mShadowBottom.draw(canvas);
448
+        }
449
+    }
450
+
451
+    public void attachToActivity(Activity activity) {
452
+        mActivity = activity;
453
+        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{
454
+                android.R.attr.windowBackground
455
+        });
456
+        int background = a.getResourceId(0, 0);
457
+        a.recycle();
458
+
459
+        ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
460
+        ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
461
+        decorChild.setBackgroundResource(background);
462
+        decor.removeView(decorChild);
463
+        addView(decorChild);
464
+        setContentView(decorChild);
465
+        decor.addView(this);
466
+    }
467
+
468
+    @Override
469
+    public void computeScroll() {
470
+        mScrimOpacity = 1 - mScrollPercent;
471
+        if (mDragHelper.continueSettling(true)) {
472
+            ViewCompat.postInvalidateOnAnimation(this);
473
+        }
474
+    }
475
+
476
+    private class ViewDragCallback extends ViewDragHelper.Callback {
477
+        private boolean mIsScrollOverValid;
478
+
479
+        @Override
480
+        public boolean tryCaptureView(View view, int i) {
481
+            boolean ret = mDragHelper.isEdgeTouched(mEdgeFlag, i);
482
+            if (ret) {
483
+                if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) {
484
+                    mTrackingEdge = EDGE_LEFT;
485
+                } else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) {
486
+                    mTrackingEdge = EDGE_RIGHT;
487
+                } else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) {
488
+                    mTrackingEdge = EDGE_BOTTOM;
489
+                }
490
+                if (mListeners != null && !mListeners.isEmpty()) {
491
+                    for (SwipeListener listener : mListeners) {
492
+                        listener.onEdgeTouch(mTrackingEdge);
493
+                    }
494
+                }
495
+                mIsScrollOverValid = true;
496
+            }
497
+            boolean directionCheck = false;
498
+            if (mEdgeFlag == EDGE_LEFT || mEdgeFlag == EDGE_RIGHT) {
499
+                directionCheck = !mDragHelper.checkTouchSlop(ViewDragHelper.DIRECTION_VERTICAL, i);
500
+            } else if (mEdgeFlag == EDGE_BOTTOM) {
501
+                directionCheck = !mDragHelper
502
+                        .checkTouchSlop(ViewDragHelper.DIRECTION_HORIZONTAL, i);
503
+            } else if (mEdgeFlag == EDGE_ALL) {
504
+                directionCheck = true;
505
+            }
506
+            return ret & directionCheck;
507
+        }
508
+
509
+        @Override
510
+        public int getViewHorizontalDragRange(View child) {
511
+            return mEdgeFlag & (EDGE_LEFT | EDGE_RIGHT);
512
+        }
513
+
514
+        @Override
515
+        public int getViewVerticalDragRange(View child) {
516
+            return mEdgeFlag & EDGE_BOTTOM;
517
+        }
518
+
519
+        @Override
520
+        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
521
+            super.onViewPositionChanged(changedView, left, top, dx, dy);
522
+            if ((mTrackingEdge & EDGE_LEFT) != 0) {
523
+                mScrollPercent = Math.abs((float) left
524
+                        / (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth()));
525
+            } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
526
+                mScrollPercent = Math.abs((float) left
527
+                        / (mContentView.getWidth() + mShadowRight.getIntrinsicWidth()));
528
+            } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
529
+                mScrollPercent = Math.abs((float) top
530
+                        / (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight()));
531
+            }
532
+            mContentLeft = left;
533
+            mContentTop = top;
534
+            invalidate();
535
+            if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) {
536
+                mIsScrollOverValid = true;
537
+            }
538
+            if (mListeners != null && !mListeners.isEmpty()
539
+                    && mDragHelper.getViewDragState() == STATE_DRAGGING
540
+                    && mScrollPercent >= mScrollThreshold && mIsScrollOverValid) {
541
+                mIsScrollOverValid = false;
542
+                for (SwipeListener listener : mListeners) {
543
+                    listener.onScrollOverThreshold();
544
+                }
545
+            }
546
+
547
+            if (mScrollPercent >= 1) {
548
+                if (!mActivity.isFinishing()) {
549
+                    mActivity.finish();
550
+                    mActivity.overridePendingTransition(0, 0);        
551
+                }
552
+            }
553
+        }
554
+
555
+        @Override
556
+        public void onViewReleased(View releasedChild, float xvel, float yvel) {
557
+            final int childWidth = releasedChild.getWidth();
558
+            final int childHeight = releasedChild.getHeight();
559
+
560
+            int left = 0, top = 0;
561
+            if ((mTrackingEdge & EDGE_LEFT) != 0) {
562
+                left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth
563
+                        + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;
564
+            } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
565
+                left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth
566
+                        + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0;
567
+            } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
568
+                top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight
569
+                        + mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0;
570
+            }
571
+
572
+            mDragHelper.settleCapturedViewAt(left, top);
573
+            invalidate();
574
+        }
575
+
576
+        @Override
577
+        public int clampViewPositionHorizontal(View child, int left, int dx) {
578
+            int ret = 0;
579
+            if ((mTrackingEdge & EDGE_LEFT) != 0) {
580
+                ret = Math.min(child.getWidth(), Math.max(left, 0));
581
+            } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
582
+                ret = Math.min(0, Math.max(left, -child.getWidth()));
583
+            }
584
+            return ret;
585
+        }
586
+
587
+        @Override
588
+        public int clampViewPositionVertical(View child, int top, int dy) {
589
+            int ret = 0;
590
+            if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
591
+                ret = Math.min(0, Math.max(top, -child.getHeight()));
592
+            }
593
+            return ret;
594
+        }
595
+
596
+        @Override
597
+        public void onViewDragStateChanged(int state) {
598
+            super.onViewDragStateChanged(state);
599
+            if (mListeners != null && !mListeners.isEmpty()) {
600
+                for (SwipeListener listener : mListeners) {
601
+                    listener.onScrollStateChange(state, mScrollPercent);
602
+                }
603
+            }
604
+        }
605
+    }
606
+}

+ 103 - 0
views/src/main/java/com/android/views/swipebacklayout/Utils.java

@@ -0,0 +1,103 @@
1
+
2
+package com.android.views.swipebacklayout;
3
+
4
+import android.app.Activity;
5
+import android.app.ActivityOptions;
6
+import android.os.Build;
7
+
8
+import java.lang.reflect.Method;
9
+
10
+/**
11
+ * Created by Chaojun Wang on 6/9/14.
12
+ */
13
+public class Utils {
14
+    private Utils() {
15
+    }
16
+
17
+    /**
18
+     * Convert a translucent themed Activity
19
+     * {@link android.R.attr#windowIsTranslucent} to a fullscreen opaque
20
+     * Activity.
21
+     * <p>
22
+     * Call this whenever the background of a translucent Activity has changed
23
+     * to become opaque. Doing so will allow the {@link android.view.Surface} of
24
+     * the Activity behind to be released.
25
+     * <p>
26
+     * This call has no effect on non-translucent activities or on activities
27
+     * with the {@link android.R.attr#windowIsFloating} attribute.
28
+     */
29
+    public static void convertActivityFromTranslucent(Activity activity) {
30
+        try {
31
+            Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
32
+            method.setAccessible(true);
33
+            method.invoke(activity);
34
+        } catch (Throwable t) {
35
+        }
36
+    }
37
+
38
+    /**
39
+     * Convert a translucent themed Activity
40
+     * {@link android.R.attr#windowIsTranslucent} back from opaque to
41
+     * translucent following a call to
42
+     * {@link #convertActivityFromTranslucent(Activity)} .
43
+     * <p>
44
+     * Calling this allows the Activity behind this one to be seen again. Once
45
+     * all such Activities have been redrawn
46
+     * <p>
47
+     * This call has no effect on non-translucent activities or on activities
48
+     * with the {@link android.R.attr#windowIsFloating} attribute.
49
+     */
50
+    public static void convertActivityToTranslucent(Activity activity) {
51
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
52
+            convertActivityToTranslucentAfterL(activity);
53
+        } else {
54
+            convertActivityToTranslucentBeforeL(activity);
55
+        }
56
+    }
57
+
58
+    /**
59
+     * Calling the convertToTranslucent method on platforms before Android 5.0
60
+     */
61
+    public static void convertActivityToTranslucentBeforeL(Activity activity) {
62
+        try {
63
+            Class<?>[] classes = Activity.class.getDeclaredClasses();
64
+            Class<?> translucentConversionListenerClazz = null;
65
+            for (Class clazz : classes) {
66
+                if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
67
+                    translucentConversionListenerClazz = clazz;
68
+                }
69
+            }
70
+            Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
71
+                    translucentConversionListenerClazz);
72
+            method.setAccessible(true);
73
+            method.invoke(activity, new Object[] {
74
+                null
75
+            });
76
+        } catch (Throwable t) {
77
+        }
78
+    }
79
+
80
+    /**
81
+     * Calling the convertToTranslucent method on platforms after Android 5.0
82
+     */
83
+    private static void convertActivityToTranslucentAfterL(Activity activity) {
84
+        try {
85
+            Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
86
+            getActivityOptions.setAccessible(true);
87
+            Object options = getActivityOptions.invoke(activity);
88
+
89
+            Class<?>[] classes = Activity.class.getDeclaredClasses();
90
+            Class<?> translucentConversionListenerClazz = null;
91
+            for (Class clazz : classes) {
92
+                if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
93
+                    translucentConversionListenerClazz = clazz;
94
+                }
95
+            }
96
+            Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
97
+                    translucentConversionListenerClazz, ActivityOptions.class);
98
+            convertToTranslucent.setAccessible(true);
99
+            convertToTranslucent.invoke(activity, null, options);
100
+        } catch (Throwable t) {
101
+        }
102
+    }
103
+}

+ 1580 - 0
views/src/main/java/com/android/views/swipebacklayout/ViewDragHelper.java

@@ -0,0 +1,1580 @@
1
+/*
2
+ * Copyright (C) 2013 The Android Open Source Project
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *      http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package com.android.views.swipebacklayout;
18
+
19
+import android.content.Context;
20
+import android.support.v4.view.MotionEventCompat;
21
+import android.support.v4.view.VelocityTrackerCompat;
22
+import android.support.v4.view.ViewCompat;
23
+import android.support.v4.widget.ScrollerCompat;
24
+import android.view.MotionEvent;
25
+import android.view.VelocityTracker;
26
+import android.view.View;
27
+import android.view.ViewConfiguration;
28
+import android.view.ViewGroup;
29
+import android.view.animation.Interpolator;
30
+
31
+import java.util.Arrays;
32
+
33
+/**
34
+ * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a
35
+ * number of useful operations and state tracking for allowing a user to drag
36
+ * and reposition views within their parent ViewGroup.
37
+ */
38
+public class ViewDragHelper {
39
+    private static final String TAG = "ViewDragHelper";
40
+
41
+    /**
42
+     * A null/invalid pointer ID.
43
+     */
44
+    public static final int INVALID_POINTER = -1;
45
+
46
+    /**
47
+     * A view is not currently being dragged or animating as a result of a
48
+     * fling/snap.
49
+     */
50
+    public static final int STATE_IDLE = 0;
51
+
52
+    /**
53
+     * A view is currently being dragged. The position is currently changing as
54
+     * a result of user input or simulated user input.
55
+     */
56
+    public static final int STATE_DRAGGING = 1;
57
+
58
+    /**
59
+     * A view is currently settling into place as a result of a fling or
60
+     * predefined non-interactive motion.
61
+     */
62
+    public static final int STATE_SETTLING = 2;
63
+
64
+    /**
65
+     * Edge flag indicating that the left edge should be affected.
66
+     */
67
+    public static final int EDGE_LEFT = 1 << 0;
68
+
69
+    /**
70
+     * Edge flag indicating that the right edge should be affected.
71
+     */
72
+    public static final int EDGE_RIGHT = 1 << 1;
73
+
74
+    /**
75
+     * Edge flag indicating that the top edge should be affected.
76
+     */
77
+    public static final int EDGE_TOP = 1 << 2;
78
+
79
+    /**
80
+     * Edge flag indicating that the bottom edge should be affected.
81
+     */
82
+    public static final int EDGE_BOTTOM = 1 << 3;
83
+
84
+    /**
85
+     * Edge flag set indicating all edges should be affected.
86
+     */
87
+    public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;
88
+
89
+    /**
90
+     * Indicates that a check should occur along the horizontal axis
91
+     */
92
+    public static final int DIRECTION_HORIZONTAL = 1 << 0;
93
+
94
+    /**
95
+     * Indicates that a check should occur along the vertical axis
96
+     */
97
+    public static final int DIRECTION_VERTICAL = 1 << 1;
98
+
99
+    /**
100
+     * Indicates that a check should occur along all axes
101
+     */
102
+    public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
103
+
104
+    public static final int EDGE_SIZE = 20; // dp
105
+
106
+    private static final int BASE_SETTLE_DURATION = 256; // ms
107
+
108
+    private static final int MAX_SETTLE_DURATION = 600; // ms
109
+
110
+    // Current drag state; idle, dragging or settling
111
+    private int mDragState;
112
+
113
+    // Distance to travel before a drag may begin
114
+    private int mTouchSlop;
115
+
116
+    // Last known position/pointer tracking
117
+    private int mActivePointerId = INVALID_POINTER;
118
+
119
+    private float[] mInitialMotionX;
120
+
121
+    private float[] mInitialMotionY;
122
+
123
+    private float[] mLastMotionX;
124
+
125
+    private float[] mLastMotionY;
126
+
127
+    private int[] mInitialEdgeTouched;
128
+
129
+    private int[] mEdgeDragsInProgress;
130
+
131
+    private int[] mEdgeDragsLocked;
132
+
133
+    private int mPointersDown;
134
+
135
+    private VelocityTracker mVelocityTracker;
136
+
137
+    private float mMaxVelocity;
138
+
139
+    private float mMinVelocity;
140
+
141
+    private int mEdgeSize;
142
+
143
+    private int mTrackingEdges;
144
+
145
+    private ScrollerCompat mScroller;
146
+
147
+    private final Callback mCallback;
148
+
149
+    private View mCapturedView;
150
+
151
+    private boolean mReleaseInProgress;
152
+
153
+    private final ViewGroup mParentView;
154
+
155
+    /**
156
+     * A Callback is used as a communication channel with the ViewDragHelper
157
+     * back to the parent view using it. <code>on*</code>methods are invoked on
158
+     * siginficant events and several accessor methods are expected to provide
159
+     * the ViewDragHelper with more information about the state of the parent
160
+     * view upon request. The callback also makes decisions governing the range
161
+     * and draggability of child views.
162
+     */
163
+    public static abstract class Callback {
164
+        /**
165
+         * Called when the drag state changes. See the <code>STATE_*</code>
166
+         * constants for more information.
167
+         *
168
+         * @param state The new drag state
169
+         * @see #STATE_IDLE
170
+         * @see #STATE_DRAGGING
171
+         * @see #STATE_SETTLING
172
+         */
173
+        public void onViewDragStateChanged(int state) {
174
+        }
175
+
176
+        /**
177
+         * Called when the captured view's position changes as the result of a
178
+         * drag or settle.
179
+         *
180
+         * @param changedView View whose position changed
181
+         * @param left        New X coordinate of the left edge of the view
182
+         * @param top         New Y coordinate of the top edge of the view
183
+         * @param dx          Change in X position from the last call
184
+         * @param dy          Change in Y position from the last call
185
+         */
186
+        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
187
+        }
188
+
189
+        /**
190
+         * Called when a child view is captured for dragging or settling. The ID
191
+         * of the pointer currently dragging the captured view is supplied. If
192
+         * activePointerId is identified as {@link #INVALID_POINTER} the capture
193
+         * is programmatic instead of pointer-initiated.
194
+         *
195
+         * @param capturedChild   Child view that was captured
196
+         * @param activePointerId Pointer id tracking the child capture
197
+         */
198
+        public void onViewCaptured(View capturedChild, int activePointerId) {
199
+        }
200
+
201
+        /**
202
+         * Called when the child view is no longer being actively dragged. The
203
+         * fling velocity is also supplied, if relevant. The velocity values may
204
+         * be clamped to system minimums or maximums.
205
+         * <p>
206
+         * Calling code may decide to fling or otherwise release the view to let
207
+         * it settle into place. It should do so using
208
+         * {@link #settleCapturedViewAt(int, int)} or
209
+         * {@link #flingCapturedView(int, int, int, int)}. If the Callback
210
+         * invokes one of these methods, the ViewDragHelper will enter
211
+         * {@link #STATE_SETTLING} and the view capture will not fully end until
212
+         * it comes to a complete stop. If neither of these methods is invoked
213
+         * before <code>onViewReleased</code> returns, the view will stop in
214
+         * place and the ViewDragHelper will return to {@link #STATE_IDLE}.
215
+         * </p>
216
+         *
217
+         * @param releasedChild The captured child view now being released
218
+         * @param xvel          X velocity of the pointer as it left the screen in pixels
219
+         *                      per second.
220
+         * @param yvel          Y velocity of the pointer as it left the screen in pixels
221
+         *                      per second.
222
+         */
223
+        public void onViewReleased(View releasedChild, float xvel, float yvel) {
224
+        }
225
+
226
+        /**
227
+         * Called when one of the subscribed edges in the parent view has been
228
+         * touched by the user while no child view is currently captured.
229
+         *
230
+         * @param edgeFlags A combination of edge flags describing the edge(s)
231
+         *                  currently touched
232
+         * @param pointerId ID of the pointer touching the described edge(s)
233
+         * @see #EDGE_LEFT
234
+         * @see #EDGE_TOP
235
+         * @see #EDGE_RIGHT
236
+         * @see #EDGE_BOTTOM
237
+         */
238
+        public void onEdgeTouched(int edgeFlags, int pointerId) {
239
+        }
240
+
241
+        /**
242
+         * Called when the given edge may become locked. This can happen if an
243
+         * edge drag was preliminarily rejected before beginning, but after
244
+         * {@link #onEdgeTouched(int, int)} was called. This method should
245
+         * return true to lock this edge or false to leave it unlocked. The
246
+         * default behavior is to leave edges unlocked.
247
+         *
248
+         * @param edgeFlags A combination of edge flags describing the edge(s)
249
+         *                  locked
250
+         * @return true to lock the edge, false to leave it unlocked
251
+         */
252
+        public boolean onEdgeLock(int edgeFlags) {
253
+            return false;
254
+        }
255
+
256
+        /**
257
+         * Called when the user has started a deliberate drag away from one of
258
+         * the subscribed edges in the parent view while no child view is
259
+         * currently captured.
260
+         *
261
+         * @param edgeFlags A combination of edge flags describing the edge(s)
262
+         *                  dragged
263
+         * @param pointerId ID of the pointer touching the described edge(s)
264
+         * @see #EDGE_LEFT
265
+         * @see #EDGE_TOP
266
+         * @see #EDGE_RIGHT
267
+         * @see #EDGE_BOTTOM
268
+         */
269
+        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
270
+        }
271
+
272
+        /**
273
+         * Called to determine the Z-order of child views.
274
+         *
275
+         * @param index the ordered position to query for
276
+         * @return index of the view that should be ordered at position
277
+         * <code>index</code>
278
+         */
279
+        public int getOrderedChildIndex(int index) {
280
+            return index;
281
+        }
282
+
283
+        /**
284
+         * Return the magnitude of a draggable child view's horizontal range of
285
+         * motion in pixels. This method should return 0 for views that cannot
286
+         * move horizontally.
287
+         *
288
+         * @param child Child view to check
289
+         * @return range of horizontal motion in pixels
290
+         */
291
+        public int getViewHorizontalDragRange(View child) {
292
+            return 0;
293
+        }
294
+
295
+        /**
296
+         * Return the magnitude of a draggable child view's vertical range of
297
+         * motion in pixels. This method should return 0 for views that cannot
298
+         * move vertically.
299
+         *
300
+         * @param child Child view to check
301
+         * @return range of vertical motion in pixels
302
+         */
303
+        public int getViewVerticalDragRange(View child) {
304
+            return 0;
305
+        }
306
+
307
+        /**
308
+         * Called when the user's input indicates that they want to capture the
309
+         * given child view with the pointer indicated by pointerId. The
310
+         * callback should return true if the user is permitted to drag the
311
+         * given view with the indicated pointer.
312
+         * <p>
313
+         * ViewDragHelper may call this method multiple times for the same view
314
+         * even if the view is already captured; this indicates that a new
315
+         * pointer is trying to take control of the view.
316
+         * </p>
317
+         * <p>
318
+         * If this method returns true, a call to
319
+         * {@link #onViewCaptured(View, int)} will follow if the
320
+         * capture is successful.
321
+         * </p>
322
+         *
323
+         * @param child     Child the user is attempting to capture
324
+         * @param pointerId ID of the pointer attempting the capture
325
+         * @return true if capture should be allowed, false otherwise
326
+         */
327
+        public abstract boolean tryCaptureView(View child, int pointerId);
328
+
329
+        /**
330
+         * Restrict the motion of the dragged child view along the horizontal
331
+         * axis. The default implementation does not allow horizontal motion;
332
+         * the extending class must override this method and provide the desired
333
+         * clamping.
334
+         *
335
+         * @param child Child view being dragged
336
+         * @param left  Attempted motion along the X axis
337
+         * @param dx    Proposed change in position for left
338
+         * @return The new clamped position for left
339
+         */
340
+        public int clampViewPositionHorizontal(View child, int left, int dx) {
341
+            return 0;
342
+        }
343
+
344
+        /**
345
+         * Restrict the motion of the dragged child view along the vertical
346
+         * axis. The default implementation does not allow vertical motion; the
347
+         * extending class must override this method and provide the desired
348
+         * clamping.
349
+         *
350
+         * @param child Child view being dragged
351
+         * @param top   Attempted motion along the Y axis
352
+         * @param dy    Proposed change in position for top
353
+         * @return The new clamped position for top
354
+         */
355
+        public int clampViewPositionVertical(View child, int top, int dy) {
356
+            return 0;
357
+        }
358
+    }
359
+
360
+    /**
361
+     * Interpolator defining the animation curve for mScroller
362
+     */
363
+    private static final Interpolator sInterpolator = new Interpolator() {
364
+        public float getInterpolation(float t) {
365
+            t -= 1.0f;
366
+            return t * t * t * t * t + 1.0f;
367
+        }
368
+    };
369
+
370
+    private final Runnable mSetIdleRunnable = new Runnable() {
371
+        public void run() {
372
+            setDragState(STATE_IDLE);
373
+        }
374
+    };
375
+
376
+    /**
377
+     * Factory method to create a new ViewDragHelper.
378
+     *
379
+     * @param forParent Parent view to monitor
380
+     * @param cb        Callback to provide information and receive events
381
+     * @return a new ViewDragHelper instance
382
+     */
383
+    public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
384
+        return new ViewDragHelper(forParent.getContext(), forParent, cb);
385
+    }
386
+
387
+    /**
388
+     * Factory method to create a new ViewDragHelper.
389
+     *
390
+     * @param forParent   Parent view to monitor
391
+     * @param sensitivity Multiplier for how sensitive the helper should be
392
+     *                    about detecting the start of a drag. Larger values are more
393
+     *                    sensitive. 1.0f is normal.
394
+     * @param cb          Callback to provide information and receive events
395
+     * @return a new ViewDragHelper instance
396
+     */
397
+    public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
398
+        final ViewDragHelper helper = create(forParent, cb);
399
+        helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
400
+        return helper;
401
+    }
402
+
403
+    /**
404
+     * Apps should use ViewDragHelper.create() to get a new instance. This will
405
+     * allow VDH to use internal compatibility implementations for different
406
+     * platform versions.
407
+     *
408
+     * @param context   Context to initialize config-dependent params from
409
+     * @param forParent Parent view to monitor
410
+     */
411
+    private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
412
+        if (forParent == null) {
413
+            throw new IllegalArgumentException("Parent view may not be null");
414
+        }
415
+        if (cb == null) {
416
+            throw new IllegalArgumentException("Callback may not be null");
417
+        }
418
+
419
+        mParentView = forParent;
420
+        mCallback = cb;
421
+
422
+        final ViewConfiguration vc = ViewConfiguration.get(context);
423
+        final float density = context.getResources().getDisplayMetrics().density;
424
+        mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
425
+
426
+        mTouchSlop = vc.getScaledTouchSlop();
427
+        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
428
+        mMinVelocity = vc.getScaledMinimumFlingVelocity();
429
+        mScroller = ScrollerCompat.create(context, sInterpolator);
430
+    }
431
+
432
+    /**
433
+     * Sets the sensitivity of the dragger.
434
+     *
435
+     * @param context     The application context.
436
+     * @param sensitivity value between 0 and 1, the final value for touchSlop =
437
+     *                    ViewConfiguration.getScaledTouchSlop * (1 / s);
438
+     */
439
+    public void setSensitivity(Context context, float sensitivity) {
440
+        float s = Math.max(0f, Math.min(1.0f, sensitivity));
441
+        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
442
+        mTouchSlop = (int) (viewConfiguration.getScaledTouchSlop() * (1 / s));
443
+    }
444
+
445
+    /**
446
+     * Set the minimum velocity that will be detected as having a magnitude
447
+     * greater than zero in pixels per second. Callback methods accepting a
448
+     * velocity will be clamped appropriately.
449
+     *
450
+     * @param minVel minimum velocity to detect
451
+     */
452
+    public void setMinVelocity(float minVel) {
453
+        mMinVelocity = minVel;
454
+    }
455
+
456
+    /**
457
+     * Set the max velocity that will be detected as having a magnitude
458
+     * greater than zero in pixels per second. Callback methods accepting a
459
+     * velocity will be clamped appropriately.
460
+     *
461
+     * @param maxVel max velocity to detect
462
+     */
463
+    public void setMaxVelocity(float maxVel) {
464
+        mMaxVelocity = maxVel;
465
+    }
466
+
467
+    /**
468
+     * Return the currently configured minimum velocity. Any flings with a
469
+     * magnitude less than this value in pixels per second. Callback methods
470
+     * accepting a velocity will receive zero as a velocity value if the real
471
+     * detected velocity was below this threshold.
472
+     *
473
+     * @return the minimum velocity that will be detected
474
+     */
475
+    public float getMinVelocity() {
476
+        return mMinVelocity;
477
+    }
478
+
479
+    /**
480
+     * Retrieve the current drag state of this helper. This will return one of
481
+     * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
482
+     *
483
+     * @return The current drag state
484
+     */
485
+    public int getViewDragState() {
486
+        return mDragState;
487
+    }
488
+
489
+    /**
490
+     * Enable edge tracking for the selected edges of the parent view. The
491
+     * callback's
492
+     * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)}
493
+     * and
494
+     * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)}
495
+     * methods will only be invoked for edges for which edge tracking has been
496
+     * enabled.
497
+     *
498
+     * @param edgeFlags Combination of edge flags describing the edges to watch
499
+     * @see #EDGE_LEFT
500
+     * @see #EDGE_TOP
501
+     * @see #EDGE_RIGHT
502
+     * @see #EDGE_BOTTOM
503
+     */
504
+    public void setEdgeTrackingEnabled(int edgeFlags) {
505
+        mTrackingEdges = edgeFlags;
506
+    }
507
+
508
+    /**
509
+     * Return the size of an edge. This is the range in pixels along the edges
510
+     * of this view that will actively detect edge touches or drags if edge
511
+     * tracking is enabled.
512
+     *
513
+     * @return The size of an edge in pixels
514
+     * @see #setEdgeTrackingEnabled(int)
515
+     */
516
+    public int getEdgeSize() {
517
+        return mEdgeSize;
518
+    }
519
+
520
+    /**
521
+     * Set the size of an edge. This is the range in pixels along the edges of
522
+     * this view that will actively detect edge touches or drags if edge
523
+     * tracking is enabled.
524
+     *
525
+     * @param size The size of an edge in pixels
526
+     */
527
+    public void setEdgeSize(int size) {
528
+        mEdgeSize = size;
529
+    }
530
+
531
+    /**
532
+     * Capture a specific child view for dragging within the parent. The
533
+     * callback will be notified but
534
+     * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#tryCaptureView(View, int)}
535
+     * will not be asked permission to capture this view.
536
+     *
537
+     * @param childView       Child view to capture
538
+     * @param activePointerId ID of the pointer that is dragging the captured
539
+     *                        child view
540
+     */
541
+    public void captureChildView(View childView, int activePointerId) {
542
+        if (childView.getParent() != mParentView) {
543
+            throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
544
+                    + "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
545
+        }
546
+
547
+        mCapturedView = childView;
548
+        mActivePointerId = activePointerId;
549
+        mCallback.onViewCaptured(childView, activePointerId);
550
+        setDragState(STATE_DRAGGING);
551
+    }
552
+
553
+    /**
554
+     * @return The currently captured view, or null if no view has been
555
+     * captured.
556
+     */
557
+    public View getCapturedView() {
558
+        return mCapturedView;
559
+    }
560
+
561
+    /**
562
+     * @return The ID of the pointer currently dragging the captured view, or
563
+     * {@link #INVALID_POINTER}.
564
+     */
565
+    public int getActivePointerId() {
566
+        return mActivePointerId;
567
+    }
568
+
569
+    /**
570
+     * @return The minimum distance in pixels that the user must travel to
571
+     * initiate a drag
572
+     */
573
+    public int getTouchSlop() {
574
+        return mTouchSlop;
575
+    }
576
+
577
+    /**
578
+     * The result of a call to this method is equivalent to
579
+     * {@link #processTouchEvent(MotionEvent)} receiving an
580
+     * ACTION_CANCEL event.
581
+     */
582
+    public void cancel() {
583
+        mActivePointerId = INVALID_POINTER;
584
+        clearMotionHistory();
585
+
586
+        if (mVelocityTracker != null) {
587
+            mVelocityTracker.recycle();
588
+            mVelocityTracker = null;
589
+        }
590
+    }
591
+
592
+    /**
593
+     * {@link #cancel()}, but also abort all motion in progress and snap to the
594
+     * end of any animation.
595
+     */
596
+    public void abort() {
597
+        cancel();
598
+        if (mDragState == STATE_SETTLING) {
599
+            final int oldX = mScroller.getCurrX();
600
+            final int oldY = mScroller.getCurrY();
601
+            mScroller.abortAnimation();
602
+            final int newX = mScroller.getCurrX();
603
+            final int newY = mScroller.getCurrY();
604
+            mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);
605
+        }
606
+        setDragState(STATE_IDLE);
607
+    }
608
+
609
+    /**
610
+     * Animate the view <code>child</code> to the given (left, top) position. If
611
+     * this method returns true, the caller should invoke
612
+     * {@link #continueSettling(boolean)} on each subsequent frame to continue
613
+     * the motion until it returns false. If this method returns false there is
614
+     * no further work to do to complete the movement.
615
+     * <p>
616
+     * This operation does not count as a capture event, though
617
+     * {@link #getCapturedView()} will still report the sliding view while the
618
+     * slide is in progress.
619
+     * </p>
620
+     *
621
+     * @param child     Child view to capture and animate
622
+     * @param finalLeft Final left position of child
623
+     * @param finalTop  Final top position of child
624
+     * @return true if animation should continue through
625
+     * {@link #continueSettling(boolean)} calls
626
+     */
627
+    public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
628
+        mCapturedView = child;
629
+        mActivePointerId = INVALID_POINTER;
630
+
631
+        return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
632
+    }
633
+
634
+    /**
635
+     * Settle the captured view at the given (left, top) position. The
636
+     * appropriate velocity from prior motion will be taken into account. If
637
+     * this method returns true, the caller should invoke
638
+     * {@link #continueSettling(boolean)} on each subsequent frame to continue
639
+     * the motion until it returns false. If this method returns false there is
640
+     * no further work to do to complete the movement.
641
+     *
642
+     * @param finalLeft Settled left edge position for the captured view
643
+     * @param finalTop  Settled top edge position for the captured view
644
+     * @return true if animation should continue through
645
+     * {@link #continueSettling(boolean)} calls
646
+     */
647
+    public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
648
+        if (!mReleaseInProgress) {
649
+            throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to "
650
+                    + "Callback#onViewReleased");
651
+        }
652
+
653
+        return forceSettleCapturedViewAt(finalLeft, finalTop,
654
+                (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
655
+                (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));
656
+    }
657
+
658
+    /**
659
+     * Settle the captured view at the given (left, top) position.
660
+     *
661
+     * @param finalLeft Target left position for the captured view
662
+     * @param finalTop  Target top position for the captured view
663
+     * @param xvel      Horizontal velocity
664
+     * @param yvel      Vertical velocity
665
+     * @return true if animation should continue through
666
+     * {@link #continueSettling(boolean)} calls
667
+     */
668
+    private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
669
+        final int startLeft = mCapturedView.getLeft();
670
+        final int startTop = mCapturedView.getTop();
671
+        final int dx = finalLeft - startLeft;
672
+        final int dy = finalTop - startTop;
673
+
674
+        if (dx == 0 && dy == 0) {
675
+            // Nothing to do. Send callbacks, be done.
676
+            mScroller.abortAnimation();
677
+            setDragState(STATE_IDLE);
678
+            return false;
679
+        }
680
+
681
+        final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
682
+        mScroller.startScroll(startLeft, startTop, dx, dy, duration);
683
+
684
+        setDragState(STATE_SETTLING);
685
+        return true;
686
+    }
687
+
688
+    private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
689
+        xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
690
+        yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
691
+        final int absDx = Math.abs(dx);
692
+        final int absDy = Math.abs(dy);
693
+        final int absXVel = Math.abs(xvel);
694
+        final int absYVel = Math.abs(yvel);
695
+        final int addedVel = absXVel + absYVel;
696
+        final int addedDistance = absDx + absDy;
697
+
698
+        final float xweight = xvel != 0 ? (float) absXVel / addedVel : (float) absDx
699
+                / addedDistance;
700
+        final float yweight = yvel != 0 ? (float) absYVel / addedVel : (float) absDy
701
+                / addedDistance;
702
+
703
+        int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
704
+        int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
705
+
706
+        return (int) (xduration * xweight + yduration * yweight);
707
+    }
708
+
709
+    private int computeAxisDuration(int delta, int velocity, int motionRange) {
710
+        if (delta == 0) {
711
+            return 0;
712
+        }
713
+
714
+        final int width = mParentView.getWidth();
715
+        final int halfWidth = width / 2;
716
+        final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
717
+        final float distance = halfWidth + halfWidth
718
+                * distanceInfluenceForSnapDuration(distanceRatio);
719
+
720
+        int duration;
721
+        velocity = Math.abs(velocity);
722
+        if (velocity > 0) {
723
+            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
724
+        } else {
725
+            final float range = (float) Math.abs(delta) / motionRange;
726
+            duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
727
+        }
728
+        return Math.min(duration, MAX_SETTLE_DURATION);
729
+    }
730
+
731
+    /**
732
+     * Clamp the magnitude of value for absMin and absMax. If the value is below
733
+     * the minimum, it will be clamped to zero. If the value is above the
734
+     * maximum, it will be clamped to the maximum.
735
+     *
736
+     * @param value  Value to clamp
737
+     * @param absMin Absolute value of the minimum significant value to return
738
+     * @param absMax Absolute value of the maximum value to return
739
+     * @return The clamped value with the same sign as <code>value</code>
740
+     */
741
+    private int clampMag(int value, int absMin, int absMax) {
742
+        final int absValue = Math.abs(value);
743
+        if (absValue < absMin)
744
+            return 0;
745
+        if (absValue > absMax)
746
+            return value > 0 ? absMax : -absMax;
747
+        return value;
748
+    }
749
+
750
+    /**
751
+     * Clamp the magnitude of value for absMin and absMax. If the value is below
752
+     * the minimum, it will be clamped to zero. If the value is above the
753
+     * maximum, it will be clamped to the maximum.
754
+     *
755
+     * @param value  Value to clamp
756
+     * @param absMin Absolute value of the minimum significant value to return
757
+     * @param absMax Absolute value of the maximum value to return
758
+     * @return The clamped value with the same sign as <code>value</code>
759
+     */
760
+    private float clampMag(float value, float absMin, float absMax) {
761
+        final float absValue = Math.abs(value);
762
+        if (absValue < absMin)
763
+            return 0;
764
+        if (absValue > absMax)
765
+            return value > 0 ? absMax : -absMax;
766
+        return value;
767
+    }
768
+
769
+    private float distanceInfluenceForSnapDuration(float f) {
770
+        f -= 0.5f; // center the values about 0.
771
+        f *= 0.3f * Math.PI / 2.0f;
772
+        return (float) Math.sin(f);
773
+    }
774
+
775
+    /**
776
+     * Settle the captured view based on standard free-moving fling behavior.
777
+     * The caller should invoke {@link #continueSettling(boolean)} on each
778
+     * subsequent frame to continue the motion until it returns false.
779
+     *
780
+     * @param minLeft Minimum X position for the view's left edge
781
+     * @param minTop  Minimum Y position for the view's top edge
782
+     * @param maxLeft Maximum X position for the view's left edge
783
+     * @param maxTop  Maximum Y position for the view's top edge
784
+     */
785
+    public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
786
+        if (!mReleaseInProgress) {
787
+            throw new IllegalStateException("Cannot flingCapturedView outside of a call to "
788
+                    + "Callback#onViewReleased");
789
+        }
790
+
791
+        mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
792
+                (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
793
+                (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
794
+                minLeft, maxLeft, minTop, maxTop);
795
+
796
+        setDragState(STATE_SETTLING);
797
+    }
798
+
799
+    /**
800
+     * Move the captured settling view by the appropriate amount for the current
801
+     * time. If <code>continueSettling</code> returns true, the caller should
802
+     * call it again on the next frame to continue.
803
+     *
804
+     * @param deferCallbacks true if state callbacks should be deferred via
805
+     *                       posted message. Set this to true if you are calling this
806
+     *                       method from {@link View#computeScroll()} or
807
+     *                       similar methods invoked as part of layout or drawing.
808
+     * @return true if settle is still in progress
809
+     */
810
+    public boolean continueSettling(boolean deferCallbacks) {
811
+        if (mDragState == STATE_SETTLING) {
812
+            boolean keepGoing = mScroller.computeScrollOffset();
813
+            final int x = mScroller.getCurrX();
814
+            final int y = mScroller.getCurrY();
815
+            final int dx = x - mCapturedView.getLeft();
816
+            final int dy = y - mCapturedView.getTop();
817
+
818
+            if (dx != 0) {
819
+                mCapturedView.offsetLeftAndRight(dx);
820
+            }
821
+            if (dy != 0) {
822
+                mCapturedView.offsetTopAndBottom(dy);
823
+            }
824
+
825
+            if (dx != 0 || dy != 0) {
826
+                mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);
827
+            }
828
+
829
+            if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {
830
+                // Close enough. The interpolator/scroller might think we're
831
+                // still moving
832
+                // but the user sure doesn't.
833
+                mScroller.abortAnimation();
834
+                keepGoing = mScroller.isFinished();
835
+            }
836
+
837
+            if (!keepGoing) {
838
+                if (deferCallbacks) {
839
+                    mParentView.post(mSetIdleRunnable);
840
+                } else {
841
+                    setDragState(STATE_IDLE);
842
+                }
843
+            }
844
+        }
845
+
846
+        return mDragState == STATE_SETTLING;
847
+    }
848
+
849
+    /**
850
+     * Like all callback events this must happen on the UI thread, but release
851
+     * involves some extra semantics. During a release (mReleaseInProgress) is
852
+     * the only time it is valid to call {@link #settleCapturedViewAt(int, int)}
853
+     * or {@link #flingCapturedView(int, int, int, int)}.
854
+     */
855
+    private void dispatchViewReleased(float xvel, float yvel) {
856
+        mReleaseInProgress = true;
857
+        mCallback.onViewReleased(mCapturedView, xvel, yvel);
858
+        mReleaseInProgress = false;
859
+
860
+        if (mDragState == STATE_DRAGGING) {
861
+            // onViewReleased didn't call a method that would have changed this.
862
+            // Go idle.
863
+            setDragState(STATE_IDLE);
864
+        }
865
+    }
866
+
867
+    private void clearMotionHistory() {
868
+        if (mInitialMotionX == null) {
869
+            return;
870
+        }
871
+        Arrays.fill(mInitialMotionX, 0);
872
+        Arrays.fill(mInitialMotionY, 0);
873
+        Arrays.fill(mLastMotionX, 0);
874
+        Arrays.fill(mLastMotionY, 0);
875
+        Arrays.fill(mInitialEdgeTouched, 0);
876
+        Arrays.fill(mEdgeDragsInProgress, 0);
877
+        Arrays.fill(mEdgeDragsLocked, 0);
878
+        mPointersDown = 0;
879
+    }
880
+
881
+    private void clearMotionHistory(int pointerId) {
882
+        if (mInitialMotionX == null) {
883
+            return;
884
+        }
885
+        mInitialMotionX[pointerId] = 0;
886
+        mInitialMotionY[pointerId] = 0;
887
+        mLastMotionX[pointerId] = 0;
888
+        mLastMotionY[pointerId] = 0;
889
+        mInitialEdgeTouched[pointerId] = 0;
890
+        mEdgeDragsInProgress[pointerId] = 0;
891
+        mEdgeDragsLocked[pointerId] = 0;
892
+        mPointersDown &= ~(1 << pointerId);
893
+    }
894
+
895
+    private void ensureMotionHistorySizeForId(int pointerId) {
896
+        if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {
897
+            float[] imx = new float[pointerId + 1];
898
+            float[] imy = new float[pointerId + 1];
899
+            float[] lmx = new float[pointerId + 1];
900
+            float[] lmy = new float[pointerId + 1];
901
+            int[] iit = new int[pointerId + 1];
902
+            int[] edip = new int[pointerId + 1];
903
+            int[] edl = new int[pointerId + 1];
904
+
905
+            if (mInitialMotionX != null) {
906
+                System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);
907
+                System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);
908
+                System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);
909
+                System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);
910
+                System.arraycopy(mInitialEdgeTouched, 0, iit, 0, mInitialEdgeTouched.length);
911
+                System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);
912
+                System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);
913
+            }
914
+
915
+            mInitialMotionX = imx;
916
+            mInitialMotionY = imy;
917
+            mLastMotionX = lmx;
918
+            mLastMotionY = lmy;
919
+            mInitialEdgeTouched = iit;
920
+            mEdgeDragsInProgress = edip;
921
+            mEdgeDragsLocked = edl;
922
+        }
923
+    }
924
+
925
+    private void saveInitialMotion(float x, float y, int pointerId) {
926
+        ensureMotionHistorySizeForId(pointerId);
927
+        mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
928
+        mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
929
+        mInitialEdgeTouched[pointerId] = getEdgeTouched((int) x, (int) y);
930
+        mPointersDown |= 1 << pointerId;
931
+    }
932
+
933
+    private void saveLastMotion(MotionEvent ev) {
934
+        final int pointerCount = MotionEventCompat.getPointerCount(ev);
935
+        for (int i = 0; i < pointerCount; i++) {
936
+            final int pointerId = MotionEventCompat.getPointerId(ev, i);
937
+            final float x = MotionEventCompat.getX(ev, i);
938
+            final float y = MotionEventCompat.getY(ev, i);
939
+            mLastMotionX[pointerId] = x;
940
+            mLastMotionY[pointerId] = y;
941
+        }
942
+    }
943
+
944
+    /**
945
+     * Check if the given pointer ID represents a pointer that is currently down
946
+     * (to the best of the ViewDragHelper's knowledge).
947
+     * <p>
948
+     * The state used to report this information is populated by the methods
949
+     * {@link #shouldInterceptTouchEvent(MotionEvent)} or
950
+     * {@link #processTouchEvent(MotionEvent)}. If one of these
951
+     * methods has not been called for all relevant MotionEvents to track, the
952
+     * information reported by this method may be stale or incorrect.
953
+     * </p>
954
+     *
955
+     * @param pointerId pointer ID to check; corresponds to IDs provided by
956
+     *                  MotionEvent
957
+     * @return true if the pointer with the given ID is still down
958
+     */
959
+    public boolean isPointerDown(int pointerId) {
960
+        return (mPointersDown & 1 << pointerId) != 0;
961
+    }
962
+
963
+    void setDragState(int state) {
964
+        if (mDragState != state) {
965
+            mDragState = state;
966
+            mCallback.onViewDragStateChanged(state);
967
+            if (state == STATE_IDLE) {
968
+                mCapturedView = null;
969
+            }
970
+        }
971
+    }
972
+
973
+    /**
974
+     * Attempt to capture the view with the given pointer ID. The callback will
975
+     * be involved. This will put us into the "dragging" state. If we've already
976
+     * captured this view with this pointer this method will immediately return
977
+     * true without consulting the callback.
978
+     *
979
+     * @param toCapture View to capture
980
+     * @param pointerId Pointer to capture with
981
+     * @return true if capture was successful
982
+     */
983
+    boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
984
+        if (toCapture == mCapturedView && mActivePointerId == pointerId) {
985
+            // Already done!
986
+            return true;
987
+        }
988
+        if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
989
+            mActivePointerId = pointerId;
990
+            captureChildView(toCapture, pointerId);
991
+            return true;
992
+        }
993
+        return false;
994
+    }
995
+
996
+    /**
997
+     * Tests scrollability within child views of v given a delta of dx.
998
+     *
999
+     * @param v      View to test for horizontal scrollability
1000
+     * @param checkV Whether the view v passed should itself be checked for
1001
+     *               scrollability (true), or just its children (false).
1002
+     * @param dx     Delta scrolled in pixels along the X axis
1003
+     * @param dy     Delta scrolled in pixels along the Y axis
1004
+     * @param x      X coordinate of the active touch point
1005
+     * @param y      Y coordinate of the active touch point
1006
+     * @return true if child views of v can be scrolled by delta of dx.
1007
+     */
1008
+    protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
1009
+        if (v instanceof ViewGroup) {
1010
+            final ViewGroup group = (ViewGroup) v;
1011
+            final int scrollX = v.getScrollX();
1012
+            final int scrollY = v.getScrollY();
1013
+            final int count = group.getChildCount();
1014
+            // Count backwards - let topmost views consume scroll distance
1015
+            // first.
1016
+            for (int i = count - 1; i >= 0; i--) {
1017
+                // TODO: Add versioned support here for transformed views.
1018
+                // This will not work for transformed views in Honeycomb+
1019
+                final View child = group.getChildAt(i);
1020
+                if (x + scrollX >= child.getLeft()
1021
+                        && x + scrollX < child.getRight()
1022
+                        && y + scrollY >= child.getTop()
1023
+                        && y + scrollY < child.getBottom()
1024
+                        && canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), y
1025
+                        + scrollY - child.getTop())) {
1026
+                    return true;
1027
+                }
1028
+            }
1029
+        }
1030
+
1031
+        return checkV
1032
+                && (ViewCompat.canScrollHorizontally(v, -dx) || ViewCompat.canScrollVertically(v,
1033
+                -dy));
1034
+    }
1035
+
1036
+    /**
1037
+     * Check if this event as provided to the parent view's
1038
+     * onInterceptTouchEvent should cause the parent to intercept the touch
1039
+     * event stream.
1040
+     *
1041
+     * @param ev MotionEvent provided to onInterceptTouchEvent
1042
+     * @return true if the parent view should return true from
1043
+     * onInterceptTouchEvent
1044
+     */
1045
+    public boolean shouldInterceptTouchEvent(MotionEvent ev) {
1046
+        final int action = MotionEventCompat.getActionMasked(ev);
1047
+        final int actionIndex = MotionEventCompat.getActionIndex(ev);
1048
+
1049
+        if (action == MotionEvent.ACTION_DOWN) {
1050
+            // Reset things for a new event stream, just in case we didn't get
1051
+            // the whole previous stream.
1052
+            cancel();
1053
+        }
1054
+
1055
+        if (mVelocityTracker == null) {
1056
+            mVelocityTracker = VelocityTracker.obtain();
1057
+        }
1058
+        mVelocityTracker.addMovement(ev);
1059
+
1060
+        switch (action) {
1061
+            case MotionEvent.ACTION_DOWN: {
1062
+                final float x = ev.getX();
1063
+                final float y = ev.getY();
1064
+                final int pointerId = MotionEventCompat.getPointerId(ev, 0);
1065
+                saveInitialMotion(x, y, pointerId);
1066
+
1067
+                final View toCapture = findTopChildUnder((int) x, (int) y);
1068
+
1069
+                // Catch a settling view if possible.
1070
+                if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
1071
+                    tryCaptureViewForDrag(toCapture, pointerId);
1072
+                }
1073
+
1074
+                final int edgesTouched = mInitialEdgeTouched[pointerId];
1075
+                if ((edgesTouched & mTrackingEdges) != 0) {
1076
+                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1077
+                }
1078
+                break;
1079
+            }
1080
+
1081
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
1082
+                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1083
+                final float x = MotionEventCompat.getX(ev, actionIndex);
1084
+                final float y = MotionEventCompat.getY(ev, actionIndex);
1085
+
1086
+                saveInitialMotion(x, y, pointerId);
1087
+
1088
+                // A ViewDragHelper can only manipulate one view at a time.
1089
+                if (mDragState == STATE_IDLE) {
1090
+                    final int edgesTouched = mInitialEdgeTouched[pointerId];
1091
+                    if ((edgesTouched & mTrackingEdges) != 0) {
1092
+                        mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1093
+                    }
1094
+                } else if (mDragState == STATE_SETTLING) {
1095
+                    // Catch a settling view if possible.
1096
+                    final View toCapture = findTopChildUnder((int) x, (int) y);
1097
+                    if (toCapture == mCapturedView) {
1098
+                        tryCaptureViewForDrag(toCapture, pointerId);
1099
+                    }
1100
+                }
1101
+                break;
1102
+            }
1103
+
1104
+            case MotionEvent.ACTION_MOVE: {
1105
+                // First to cross a touch slop over a draggable view wins. Also
1106
+                // report edge drags.
1107
+                final int pointerCount = MotionEventCompat.getPointerCount(ev);
1108
+                for (int i = 0; i < pointerCount; i++) {
1109
+                    final int pointerId = MotionEventCompat.getPointerId(ev, i);
1110
+                    final float x = MotionEventCompat.getX(ev, i);
1111
+                    final float y = MotionEventCompat.getY(ev, i);
1112
+                    final float dx = x - mInitialMotionX[pointerId];
1113
+                    final float dy = y - mInitialMotionY[pointerId];
1114
+
1115
+                    reportNewEdgeDrags(dx, dy, pointerId);
1116
+                    if (mDragState == STATE_DRAGGING) {
1117
+                        // Callback might have started an edge drag
1118
+                        break;
1119
+                    }
1120
+
1121
+                    final View toCapture = findTopChildUnder((int) x, (int) y);
1122
+                    if (toCapture != null && checkTouchSlop(toCapture, dx, dy)
1123
+                            && tryCaptureViewForDrag(toCapture, pointerId)) {
1124
+                        break;
1125
+                    }
1126
+                }
1127
+                saveLastMotion(ev);
1128
+                break;
1129
+            }
1130
+
1131
+            case MotionEventCompat.ACTION_POINTER_UP: {
1132
+                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1133
+                clearMotionHistory(pointerId);
1134
+                break;
1135
+            }
1136
+
1137
+            case MotionEvent.ACTION_UP:
1138
+            case MotionEvent.ACTION_CANCEL: {
1139
+                cancel();
1140
+                break;
1141
+            }
1142
+        }
1143
+
1144
+        return mDragState == STATE_DRAGGING;
1145
+    }
1146
+
1147
+    /**
1148
+     * Process a touch event received by the parent view. This method will
1149
+     * dispatch callback events as needed before returning. The parent view's
1150
+     * onTouchEvent implementation should call this.
1151
+     *
1152
+     * @param ev The touch event received by the parent view
1153
+     */
1154
+    public void processTouchEvent(MotionEvent ev) {
1155
+        final int action = MotionEventCompat.getActionMasked(ev);
1156
+        final int actionIndex = MotionEventCompat.getActionIndex(ev);
1157
+
1158
+        if (action == MotionEvent.ACTION_DOWN) {
1159
+            // Reset things for a new event stream, just in case we didn't get
1160
+            // the whole previous stream.
1161
+            cancel();
1162
+        }
1163
+
1164
+        if (mVelocityTracker == null) {
1165
+            mVelocityTracker = VelocityTracker.obtain();
1166
+        }
1167
+        mVelocityTracker.addMovement(ev);
1168
+
1169
+        switch (action) {
1170
+            case MotionEvent.ACTION_DOWN: {
1171
+                final float x = ev.getX();
1172
+                final float y = ev.getY();
1173
+                final int pointerId = MotionEventCompat.getPointerId(ev, 0);
1174
+                final View toCapture = findTopChildUnder((int) x, (int) y);
1175
+
1176
+                saveInitialMotion(x, y, pointerId);
1177
+
1178
+                // Since the parent is already directly processing this touch
1179
+                // event,
1180
+                // there is no reason to delay for a slop before dragging.
1181
+                // Start immediately if possible.
1182
+                tryCaptureViewForDrag(toCapture, pointerId);
1183
+
1184
+                final int edgesTouched = mInitialEdgeTouched[pointerId];
1185
+                if ((edgesTouched & mTrackingEdges) != 0) {
1186
+                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1187
+                }
1188
+                break;
1189
+            }
1190
+
1191
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
1192
+                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1193
+                final float x = MotionEventCompat.getX(ev, actionIndex);
1194
+                final float y = MotionEventCompat.getY(ev, actionIndex);
1195
+
1196
+                saveInitialMotion(x, y, pointerId);
1197
+
1198
+                // A ViewDragHelper can only manipulate one view at a time.
1199
+                if (mDragState == STATE_IDLE) {
1200
+                    // If we're idle we can do anything! Treat it like a normal
1201
+                    // down event.
1202
+
1203
+                    final View toCapture = findTopChildUnder((int) x, (int) y);
1204
+                    tryCaptureViewForDrag(toCapture, pointerId);
1205
+
1206
+                    final int edgesTouched = mInitialEdgeTouched[pointerId];
1207
+                    if ((edgesTouched & mTrackingEdges) != 0) {
1208
+                        mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
1209
+                    }
1210
+                } else if (isCapturedViewUnder((int) x, (int) y)) {
1211
+                    // We're still tracking a captured view. If the same view is
1212
+                    // under this
1213
+                    // point, we'll swap to controlling it with this pointer
1214
+                    // instead.
1215
+                    // (This will still work if we're "catching" a settling
1216
+                    // view.)
1217
+
1218
+                    tryCaptureViewForDrag(mCapturedView, pointerId);
1219
+                }
1220
+                break;
1221
+            }
1222
+
1223
+            case MotionEvent.ACTION_MOVE: {
1224
+                if (mDragState == STATE_DRAGGING) {
1225
+                    final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1226
+                    final float x = MotionEventCompat.getX(ev, index);
1227
+                    final float y = MotionEventCompat.getY(ev, index);
1228
+                    final int idx = (int) (x - mLastMotionX[mActivePointerId]);
1229
+                    final int idy = (int) (y - mLastMotionY[mActivePointerId]);
1230
+
1231
+                    dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
1232
+
1233
+                    saveLastMotion(ev);
1234
+                } else {
1235
+                    // Check to see if any pointer is now over a draggable view.
1236
+                    final int pointerCount = MotionEventCompat.getPointerCount(ev);
1237
+                    for (int i = 0; i < pointerCount; i++) {
1238
+                        final int pointerId = MotionEventCompat.getPointerId(ev, i);
1239
+                        final float x = MotionEventCompat.getX(ev, i);
1240
+                        final float y = MotionEventCompat.getY(ev, i);
1241
+                        final float dx = x - mInitialMotionX[pointerId];
1242
+                        final float dy = y - mInitialMotionY[pointerId];
1243
+
1244
+                        reportNewEdgeDrags(dx, dy, pointerId);
1245
+                        if (mDragState == STATE_DRAGGING) {
1246
+                            // Callback might have started an edge drag.
1247
+                            break;
1248
+                        }
1249
+
1250
+                        final View toCapture = findTopChildUnder((int) x, (int) y);
1251
+                        if (checkTouchSlop(toCapture, dx, dy)
1252
+                                && tryCaptureViewForDrag(toCapture, pointerId)) {
1253
+                            break;
1254
+                        }
1255
+                    }
1256
+                    saveLastMotion(ev);
1257
+                }
1258
+                break;
1259
+            }
1260
+
1261
+            case MotionEventCompat.ACTION_POINTER_UP: {
1262
+                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
1263
+                if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
1264
+                    // Try to find another pointer that's still holding on to
1265
+                    // the captured view.
1266
+                    int newActivePointer = INVALID_POINTER;
1267
+                    final int pointerCount = MotionEventCompat.getPointerCount(ev);
1268
+                    for (int i = 0; i < pointerCount; i++) {
1269
+                        final int id = MotionEventCompat.getPointerId(ev, i);
1270
+                        if (id == mActivePointerId) {
1271
+                            // This one's going away, skip.
1272
+                            continue;
1273
+                        }
1274
+
1275
+                        final float x = MotionEventCompat.getX(ev, i);
1276
+                        final float y = MotionEventCompat.getY(ev, i);
1277
+                        if (findTopChildUnder((int) x, (int) y) == mCapturedView
1278
+                                && tryCaptureViewForDrag(mCapturedView, id)) {
1279
+                            newActivePointer = mActivePointerId;
1280
+                            break;
1281
+                        }
1282
+                    }
1283
+
1284
+                    if (newActivePointer == INVALID_POINTER) {
1285
+                        // We didn't find another pointer still touching the
1286
+                        // view, release it.
1287
+                        releaseViewForPointerUp();
1288
+                    }
1289
+                }
1290
+                clearMotionHistory(pointerId);
1291
+                break;
1292
+            }
1293
+
1294
+            case MotionEvent.ACTION_UP: {
1295
+                if (mDragState == STATE_DRAGGING) {
1296
+                    releaseViewForPointerUp();
1297
+                }
1298
+                cancel();
1299
+                break;
1300
+            }
1301
+
1302
+            case MotionEvent.ACTION_CANCEL: {
1303
+                if (mDragState == STATE_DRAGGING) {
1304
+                    dispatchViewReleased(0, 0);
1305
+                }
1306
+                cancel();
1307
+                break;
1308
+            }
1309
+        }
1310
+    }
1311
+
1312
+    private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
1313
+        int dragsStarted = 0;
1314
+        if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
1315
+            dragsStarted |= EDGE_LEFT;
1316
+        }
1317
+        if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
1318
+            dragsStarted |= EDGE_TOP;
1319
+        }
1320
+        if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
1321
+            dragsStarted |= EDGE_RIGHT;
1322
+        }
1323
+        if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
1324
+            dragsStarted |= EDGE_BOTTOM;
1325
+        }
1326
+
1327
+        if (dragsStarted != 0) {
1328
+            mEdgeDragsInProgress[pointerId] |= dragsStarted;
1329
+            mCallback.onEdgeDragStarted(dragsStarted, pointerId);
1330
+        }
1331
+    }
1332
+
1333
+    private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
1334
+        final float absDelta = Math.abs(delta);
1335
+        final float absODelta = Math.abs(odelta);
1336
+
1337
+        if ((mInitialEdgeTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0
1338
+                || (mEdgeDragsLocked[pointerId] & edge) == edge
1339
+                || (mEdgeDragsInProgress[pointerId] & edge) == edge
1340
+                || (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
1341
+            return false;
1342
+        }
1343
+        if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
1344
+            mEdgeDragsLocked[pointerId] |= edge;
1345
+            return false;
1346
+        }
1347
+        return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
1348
+    }
1349
+
1350
+    /**
1351
+     * Check if we've crossed a reasonable touch slop for the given child view.
1352
+     * If the child cannot be dragged along the horizontal or vertical axis,
1353
+     * motion along that axis will not count toward the slop check.
1354
+     *
1355
+     * @param child Child to check
1356
+     * @param dx    Motion since initial position along X axis
1357
+     * @param dy    Motion since initial position along Y axis
1358
+     * @return true if the touch slop has been crossed
1359
+     */
1360
+    private boolean checkTouchSlop(View child, float dx, float dy) {
1361
+        if (child == null) {
1362
+            return false;
1363
+        }
1364
+        final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
1365
+        final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
1366
+
1367
+        if (checkHorizontal && checkVertical) {
1368
+            return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
1369
+        } else if (checkHorizontal) {
1370
+            return Math.abs(dx) > mTouchSlop;
1371
+        } else if (checkVertical) {
1372
+            return Math.abs(dy) > mTouchSlop;
1373
+        }
1374
+        return false;
1375
+    }
1376
+
1377
+    /**
1378
+     * Check if any pointer tracked in the current gesture has crossed the
1379
+     * required slop threshold.
1380
+     * <p>
1381
+     * This depends on internal state populated by
1382
+     * {@link #shouldInterceptTouchEvent(MotionEvent)} or
1383
+     * {@link #processTouchEvent(MotionEvent)}. You should only
1384
+     * rely on the results of this method after all currently available touch
1385
+     * data has been provided to one of these two methods.
1386
+     * </p>
1387
+     *
1388
+     * @param directions Combination of direction flags, see
1389
+     *                   {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL},
1390
+     *                   {@link #DIRECTION_ALL}
1391
+     * @return true if the slop threshold has been crossed, false otherwise
1392
+     */
1393
+    public boolean checkTouchSlop(int directions) {
1394
+        final int count = mInitialMotionX.length;
1395
+        for (int i = 0; i < count; i++) {
1396
+            if (checkTouchSlop(directions, i)) {
1397
+                return true;
1398
+            }
1399
+        }
1400
+        return false;
1401
+    }
1402
+
1403
+    /**
1404
+     * Check if the specified pointer tracked in the current gesture has crossed
1405
+     * the required slop threshold.
1406
+     * <p>
1407
+     * This depends on internal state populated by
1408
+     * {@link #shouldInterceptTouchEvent(MotionEvent)} or
1409
+     * {@link #processTouchEvent(MotionEvent)}. You should only
1410
+     * rely on the results of this method after all currently available touch
1411
+     * data has been provided to one of these two methods.
1412
+     * </p>
1413
+     *
1414
+     * @param directions Combination of direction flags, see
1415
+     *                   {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL},
1416
+     *                   {@link #DIRECTION_ALL}
1417
+     * @param pointerId  ID of the pointer to slop check as specified by
1418
+     *                   MotionEvent
1419
+     * @return true if the slop threshold has been crossed, false otherwise
1420
+     */
1421
+    public boolean checkTouchSlop(int directions, int pointerId) {
1422
+        if (!isPointerDown(pointerId)) {
1423
+            return false;
1424
+        }
1425
+
1426
+        final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL;
1427
+        final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL;
1428
+
1429
+        final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId];
1430
+        final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId];
1431
+
1432
+        if (checkHorizontal && checkVertical) {
1433
+            return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
1434
+        } else if (checkHorizontal) {
1435
+            return Math.abs(dx) > mTouchSlop;
1436
+        } else if (checkVertical) {
1437
+            return Math.abs(dy) > mTouchSlop;
1438
+        }
1439
+        return false;
1440
+    }
1441
+
1442
+    /**
1443
+     * Check if any of the edges specified were initially touched in the
1444
+     * currently active gesture. If there is no currently active gesture this
1445
+     * method will return false.
1446
+     *
1447
+     * @param edges Edges to check for an initial edge touch. See
1448
+     *              {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT},
1449
+     *              {@link #EDGE_BOTTOM} and {@link #EDGE_ALL}
1450
+     * @return true if any of the edges specified were initially touched in the
1451
+     * current gesture
1452
+     */
1453
+    public boolean isEdgeTouched(int edges) {
1454
+        final int count = mInitialEdgeTouched.length;
1455
+        for (int i = 0; i < count; i++) {
1456
+            if (isEdgeTouched(edges, i)) {
1457
+                return true;
1458
+            }
1459
+        }
1460
+        return false;
1461
+    }
1462
+
1463
+    /**
1464
+     * Check if any of the edges specified were initially touched by the pointer
1465
+     * with the specified ID. If there is no currently active gesture or if
1466
+     * there is no pointer with the given ID currently down this method will
1467
+     * return false.
1468
+     *
1469
+     * @param edges Edges to check for an initial edge touch. See
1470
+     *              {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT},
1471
+     *              {@link #EDGE_BOTTOM} and {@link #EDGE_ALL}
1472
+     * @return true if any of the edges specified were initially touched in the
1473
+     * current gesture
1474
+     */
1475
+    public boolean isEdgeTouched(int edges, int pointerId) {
1476
+        return isPointerDown(pointerId) && (mInitialEdgeTouched[pointerId] & edges) != 0;
1477
+    }
1478
+
1479
+    private void releaseViewForPointerUp() {
1480
+        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
1481
+        final float xvel = clampMag(
1482
+                VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),
1483
+                mMinVelocity, mMaxVelocity);
1484
+        final float yvel = clampMag(
1485
+                VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),
1486
+                mMinVelocity, mMaxVelocity);
1487
+        dispatchViewReleased(xvel, yvel);
1488
+    }
1489
+
1490
+    private void dragTo(int left, int top, int dx, int dy) {
1491
+        int clampedX = left;
1492
+        int clampedY = top;
1493
+        final int oldLeft = mCapturedView.getLeft();
1494
+        final int oldTop = mCapturedView.getTop();
1495
+        if (dx != 0) {
1496
+            clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
1497
+            mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
1498
+        }
1499
+        if (dy != 0) {
1500
+            clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
1501
+            mCapturedView.offsetTopAndBottom(clampedY - oldTop);
1502
+        }
1503
+
1504
+        if (dx != 0 || dy != 0) {
1505
+            final int clampedDx = clampedX - oldLeft;
1506
+            final int clampedDy = clampedY - oldTop;
1507
+            mCallback
1508
+                    .onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy);
1509
+        }
1510
+    }
1511
+
1512
+    /**
1513
+     * Determine if the currently captured view is under the given point in the
1514
+     * parent view's coordinate system. If there is no captured view this method
1515
+     * will return false.
1516
+     *
1517
+     * @param x X position to test in the parent's coordinate system
1518
+     * @param y Y position to test in the parent's coordinate system
1519
+     * @return true if the captured view is under the given point, false
1520
+     * otherwise
1521
+     */
1522
+    public boolean isCapturedViewUnder(int x, int y) {
1523
+        return isViewUnder(mCapturedView, x, y);
1524
+    }
1525
+
1526
+    /**
1527
+     * Determine if the supplied view is under the given point in the parent
1528
+     * view's coordinate system.
1529
+     *
1530
+     * @param view Child view of the parent to hit test
1531
+     * @param x    X position to test in the parent's coordinate system
1532
+     * @param y    Y position to test in the parent's coordinate system
1533
+     * @return true if the supplied view is under the given point, false
1534
+     * otherwise
1535
+     */
1536
+    public boolean isViewUnder(View view, int x, int y) {
1537
+        if (view == null) {
1538
+            return false;
1539
+        }
1540
+        return x >= view.getLeft() && x < view.getRight() && y >= view.getTop()
1541
+                && y < view.getBottom();
1542
+    }
1543
+
1544
+    /**
1545
+     * Find the topmost child under the given point within the parent view's
1546
+     * coordinate system. The child order is determined using
1547
+     * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#getOrderedChildIndex(int)}
1548
+     * .
1549
+     *
1550
+     * @param x X position to test in the parent's coordinate system
1551
+     * @param y Y position to test in the parent's coordinate system
1552
+     * @return The topmost child view under (x, y) or null if none found.
1553
+     */
1554
+    public View findTopChildUnder(int x, int y) {
1555
+        final int childCount = mParentView.getChildCount();
1556
+        for (int i = childCount - 1; i >= 0; i--) {
1557
+            final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
1558
+            if (x >= child.getLeft() && x < child.getRight() && y >= child.getTop()
1559
+                    && y < child.getBottom()) {
1560
+                return child;
1561
+            }
1562
+        }
1563
+        return null;
1564
+    }
1565
+
1566
+    private int getEdgeTouched(int x, int y) {
1567
+        int result = 0;
1568
+
1569
+        if (x < mParentView.getLeft() + mEdgeSize)
1570
+            result = EDGE_LEFT;
1571
+        if (y < mParentView.getTop() + mEdgeSize)
1572
+            result = EDGE_TOP;
1573
+        if (x > mParentView.getRight() - mEdgeSize)
1574
+            result = EDGE_RIGHT;
1575
+        if (y > mParentView.getBottom() - mEdgeSize)
1576
+            result = EDGE_BOTTOM;
1577
+
1578
+        return result;
1579
+    }
1580
+}

BIN
views/src/main/res/drawable/shadow_bottom.png


BIN
views/src/main/res/drawable/shadow_left.png


BIN
views/src/main/res/drawable/shadow_right.png


+ 6 - 0
views/src/main/res/layout/swipeback_layout.xml

@@ -0,0 +1,6 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<com.android.views.swipebacklayout.SwipeBackLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    android:id="@+id/swipe"
4
+    android:layout_width="match_parent"
5
+    android:layout_height="match_parent" />
6
+    

+ 14 - 0
views/src/main/res/values/attrs.xml

@@ -73,4 +73,18 @@
73 73
         </attr>
74 74
     </declare-styleable>
75 75
 
76
+    <declare-styleable name="SwipeBackLayout">
77
+        <attr name="edge_size" format="dimension"/>
78
+        <attr name="edge_flag">
79
+            <enum name="left" value="0" />
80
+            <enum name="right" value="1" />
81
+            <enum name="bottom" value="2" />
82
+            <enum name="all" value="3" />
83
+        </attr>
84
+        <attr name="shadow_left" format="reference"/>
85
+        <attr name="shadow_right" format="reference"/>
86
+        <attr name="shadow_bottom" format="reference"/>
87
+    </declare-styleable>
88
+
89
+    <attr name="SwipeBackLayoutStyle" format="reference"/>
76 90
 </resources>

+ 8 - 17
views/src/main/res/values/styles.xml

@@ -6,21 +6,12 @@
6 6
         <item name="android:background">@color/line_bg</item>
7 7
         <item name="android:layout_margin">2dp</item>
8 8
     </style>
9
-    <declare-styleable name="SwipeLayout">
10
-        <attr name="drag_edge">
11
-            <flag name="left" value="1" />
12
-            <flag name="right" value="2" />
13
-            <flag name="top" value="4" />
14
-            <flag name="bottom" value="8" />
15
-        </attr>
16
-        <attr name="leftEdgeSwipeOffset" format="dimension" />
17
-        <attr name="rightEdgeSwipeOffset" format="dimension" />
18
-        <attr name="topEdgeSwipeOffset" format="dimension" />
19
-        <attr name="bottomEdgeSwipeOffset" format="dimension" />
20
-        <attr name="show_mode" format="enum">
21
-            <enum name="lay_down" value="0" />
22
-            <enum name="pull_out" value="1" />
23
-        </attr>
24
-        <attr name="clickToClose" format="boolean" />
25
-    </declare-styleable>
9
+
10
+    <style name="SwipeBackLayout">
11
+        <item name="edge_size">50dip</item>
12
+        <item name="shadow_left">@drawable/shadow_left</item>
13
+        <item name="shadow_right">@drawable/shadow_right</item>
14
+        <item name="shadow_bottom">@drawable/shadow_bottom</item>
15
+    </style>
16
+
26 17
 </resources>

kodo - Gogs: Go Git Service

Няма описание

tourguidegroupuser_views.py 11KB

    # -*- coding: utf-8 -*- from __future__ import division import json from django.conf import settings from django.db import transaction from logit import logit from TimeConvert import TimeConvert as tc from account.models import UserInfo from group.models import GroupInfo, GroupUserInfo from utils.error.errno_utils import GroupStatusCode, GroupUserStatusCode, UserStatusCode from utils.error.response_utils import response from utils.group_photo_utils import get_current_photos from utils.redis.connect import r from utils.redis.rgroup import get_group_info, get_group_users_info, get_group_users_kv_info, set_group_users_info from utils.redis.rkeys import (GROUP_LAST_PHOTO_PK, GROUP_USERS_DELETED_SET, GROUP_USERS_PASSED_SET, GROUP_USERS_QUIT_SET, GROUP_USERS_REFUSED_SET, TOUR_GUIDE_GROUP_CUR_GATHER_INFO, TOUR_GUIDE_GROUP_CUR_SESSION, TOUR_GUIDE_GROUP_GEO_INFO, TOUR_GUIDE_GROUP_GEO_SUBMIT_DT, TOUR_GUIDE_GROUP_USER_GEO_LIST) from utils.redis.rtourguide import get_tour_guide_own_group from utils.redis.rtouruser import set_tour_user_belong_group @logit(res=settings.LOGIT_RES_FLAG) def tgu_group_user_join_api(request): """ 旅行团成员加团 """ admin_id = request.POST.get('admin_id', '') # 导游唯一标识,识别二维码获取 user_id = request.POST.get('user_id', '') nickname = request.POST.get('nickname', '') name = request.POST.get('name', '') phone = request.POST.get('phone', '') relative_persons = int(request.POST.get('relative_persons', 1)) authority = bool(int(request.POST.get('authority', 1))) remark = request.POST.get('remark', '') # 获取旅行团唯一标识 group_id = get_tour_guide_own_group(admin_id) # 用户校验 try: user = UserInfo.objects.get(user_id=user_id) except UserInfo.DoesNotExist: return response(UserStatusCode.USER_NOT_FOUND) # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 群组锁定校验 if group.group_lock: return response(GroupStatusCode.GROUP_HAS_LOCKED) # 群组用户记录创建,若记录不存在,则创建,若记录已存在,则更新 group_user, created = GroupUserInfo.objects.get_or_create( group_id=group_id, user_id=user_id, defaults={ 'name': name, 'phone': phone, 'relative_persons': relative_persons, 'authority': authority, 'remark': remark, } ) if not created: group_user.name = name group_user.phone = phone group_user.relative_persons = relative_persons group_user.authority = authority group_user.remark = remark group_user.save() if group_user.user_status != GroupUserInfo.PASSED: group_user.current_id = int(r.get(GROUP_LAST_PHOTO_PK % group_id) or -1) group_user.nickname = nickname or user.final_nickname group_user.avatar = user.avatar # group_user.admin = False # Admin Field Default False, Should Not Assign group_user.user_status = GroupUserInfo.PASSED group_user.passed_at = tc.utc_datetime() group_user.save() # Redis 群组用户数据缓存 set_group_users_info(group) # Redis 群组通过集合缓存 r.srem(GROUP_USERS_REFUSED_SET % group_id, user_id) r.srem(GROUP_USERS_DELETED_SET % group_id, user_id) r.srem(GROUP_USERS_QUIT_SET % group_id, user_id) r.sadd(GROUP_USERS_PASSED_SET % group_id, user_id) curinfo = get_current_photos(group_id, user_id, group_user.current_id) # 添加默认地理位置信息 r.geoadd(TOUR_GUIDE_GROUP_GEO_INFO % group_id, 0, 0, user_id) # 设置旅行团成员所属的旅行团 set_tour_user_belong_group(user_id, group_id) return response(200, 'Tour Guide User Join Success', u'旅行团成员加团成功', { 'current_id': curinfo.get('current_id', ''), 'photos': curinfo.get('photos', ''), 'group_id': group_id, 'group': get_group_info(group_id), 'user_id': user_id, 'users': get_group_users_info(group_id, user_id), }) @logit def tgu_group_user_remove_api(request): """ 旅行团成员移除,管理员主动,团成员被动 """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') # 导游唯一标识 user_id = request.POST.get('user_id', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists() or admin_id == user_id: # 管理员也不允许将自己移除 return response(GroupStatusCode.NO_UPDATE_PERMISSION) # 群组用户校验 try: group_user = GroupUserInfo.objects.get(group_id=group_id, user_id=user_id, status=True) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 群组用户移除 group_user.user_status = GroupUserInfo.DELETED group_user.deleted_at = tc.utc_datetime() group_user.save() # Redis 群组数据缓存更新 group_users = set_group_users_info(group) # Redis 群组删除集合缓存 r.srem(GROUP_USERS_PASSED_SET % group_id, user_id) r.sadd(GROUP_USERS_DELETED_SET % group_id, user_id) # 移除地理位置信息 r.georem(TOUR_GUIDE_GROUP_GEO_INFO % group_id, user_id) return response(200, 'Tour Guide User Remove Success', u'旅行团成员移除成功', { 'group_id': group_id, 'users': group_users, }) @logit @transaction.atomic def tgu_group_user_update_api(request): """ 旅行团成员信息更新 """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') # 导游唯一标识 user_id = request.POST.get('user_id', '') name = request.POST.get('name', '') phone = request.POST.get('phone', '') relative_persons = int(request.POST.get('relative_persons', 0)) authority = bool(int(request.POST.get('authority', 0))) remark = request.POST.get('remark', '') # 群组校验 try: group = GroupInfo.objects.get(group_id=group_id) except GroupInfo.DoesNotExist: return response(GroupStatusCode.GROUP_NOT_FOUND) # 权限校验 if admin_id: if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists(): return response(GroupStatusCode.NO_UPDATE_PERMISSION) else: if not GroupUserInfo.objects.filter(group_id=group_id, user_id=user_id, status=True).exists(): return response(GroupStatusCode.NO_UPDATE_PERMISSION) # 权限 try: group_user = GroupUserInfo.objects.select_for_update().get(group_id=group_id, user_id=user_id, status=True) except GroupUserInfo.DoesNotExist: return response(GroupUserStatusCode.GROUP_USER_NOT_FOUND) # 用户信息更新 # TODO: Whether sync name and phone to UserInfo or not? if name: group_user.name = name if phone: group_user.phone = phone if relative_persons: # TODO & UNDO: Should check not gt GroupInfo.total_persons & App remind group_user.relative_persons = relative_persons if authority: group_user.authority = authority if remark: group_user.remark = remark group_user.save() # Redis 群组用户数据缓存 group_users = set_group_users_info(group) return response(200, 'Tour Guide User Update Success', u'旅行团成员信息更新成功', { 'group_id': group_id, 'group': group.data, 'users': group_users, }) def get_geo_submit_flag(geo_at, gather_at): """ 是否上传过位置字段(即是否失联) """ if geo_at and gather_at: geo_at = tc.utc_string_to_utc_datetime(geo_at, format='%Y-%m-%dT%H:%M:%SZ') gather_at = tc.utc_string_to_utc_datetime(gather_at, format='%Y-%m-%dT%H:%M:%SZ') current_dt = tc.utc_datetime() delta_seconds = tc.total_seconds(gather_at - current_dt) # 距离集合时间超过30分钟是5分钟,15分钟到30分钟是3分钟,15分钟以内是1分钟 for delta, gdt in [(1800, 300), (900, 180), (0, 60)]: if delta_seconds > delta: return tc.total_seconds(current_dt - geo_at) <= gdt return False @logit def tgu_group_user_locations_api(request): """ 旅行团所有成员位置信息 """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') # 导游唯一标识 # 权限校验 if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists(): return response(GroupStatusCode.NO_LOCATION_PERMISSION) # 获取集合经纬度 gather_info = json.loads(r.get(TOUR_GUIDE_GROUP_CUR_GATHER_INFO % group_id) or '{}') # GEO submit dts geo_dts = r.hgetall(TOUR_GUIDE_GROUP_GEO_SUBMIT_DT % group_id) # [['x', 0.33, (2.68220901489e-06, 1.26736058093e-06)], []] locations = r.georadius(TOUR_GUIDE_GROUP_GEO_INFO % group_id, gather_info.get('gather_lon', 0), gather_info.get('gather_lat', 0), '+inf', unit='m', withdist=True, withcoord=True, sort='ASC') # [{'lon': 2.68220901489e-06, 'lat': 26736058093e-06, 'dist': 0.33, etc...}, {}] # 获取旅行团用户 KV 信息 group_users_kv_info = get_group_users_kv_info(group_id) locations = [dict(group_users_kv_info[loc[0]], **{ 'lon': loc[2][0], 'lat': loc[2][1], 'dist': loc[1], 'geo_submited': get_geo_submit_flag(geo_dts.get(loc[0], ''), gather_info.get('gather_at', '')), }) for loc in locations] return response(200, 'Get Tour Guide Group All User Location Success', u'获取旅行团成员地理位置信息成功', { 'group_id': group_id, 'locations': locations, }) @logit def tgu_group_user_location_api(request): """ 旅行团单个成员位置信息 """ group_id = request.POST.get('group_id', '') admin_id = request.POST.get('admin_id', '') # 导游唯一标识 user_id = request.POST.get('user_id', '') # 权限校验 if not GroupUserInfo.objects.filter(group_id=group_id, user_id=admin_id, subadmin=True, status=True).exists(): return response(GroupStatusCode.NO_LOCATION_PERMISSION) session_id = r.get(TOUR_GUIDE_GROUP_CUR_SESSION % group_id) locations = r.lrange(TOUR_GUIDE_GROUP_USER_GEO_LIST % (group_id, session_id, user_id), 0, -1) return response(200, 'Get Tour Guide Group User Location Success', u'获取旅行团成员地理位置信息成功', { 'group_id': group_id, 'user_id': user_id, 'locations': [json.loads(loc) for loc in locations] })