編輯:關於android開發
一、項目概要
1.1 項目效果如圖:

1.2 需要使用到的技術
ViewDragHelper: 要實現和QQ5.0側滑的特效,需要借助谷歌在2013年I/O大會上發布的ViewDragHelper類,提供這個類目的就是為了解決拖拽滑動問題
1.3 側滑菜單的實現方式
1. SlidingMenu 第三方庫
2. DrawerLayout v4包中的類
3. 自定義控件
1.4 一些回調方法
- tryCaptureView: 用來決定是否可以拖動
- clampViewPositionHorizontal: 用來設置子控件將要顯示的位置 [限制子控件拖動的范圍]
- getViewHorizontalDragRange:返回水平方向拖動的最大范圍,返回大於0的值才可以拖動
- onViewPositionChanged: 位置改變時調用 [關聯菜單與主界面的滑動,監聽拖動狀態,伴隨動畫]
- onViewReleased: 拖動結束後,松開手時調用 [平滑地打開或關閉側滑菜單]
二、項目實現
2.1 創建DragLayout
1 public class DragLayout extends FrameLayout {
2 public DragLayout(Context context) {
3 super(context);
4 }
5 public DragLayout(Context context, AttributeSet attrs) {
6 super(context, attrs);
7 }
8 }
2.2 創建側滑面板布局
1 <com.xiaowu.draglayout.view.DragLayout 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:id="@+id/drag_layout" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:background="@drawable/bg" > 8 9 <!-- 側滑菜單布局 --> 10 <LinearLayout 11 android:layout_width="match_parent" 12 android:layout_height="match_parent" 13 android:background="#33ff0000" /> 14 15 <!-- 主界面布局 --> 16 <LinearLayout 17 android:layout_width="match_parent" 18 android:layout_height="match_parent" 19 android:background="#3300ff00" /> 20 21 </com.xiaowu.draglayout.view.DragLayout>
2.3 DragLayout的主程序代碼,下面代碼中有詳細的講解,我就不多分步驟實現了
1 package com.xiaowu.draglayout.view;
2
3 import android.content.Context;
4 import android.graphics.Color;
5 import android.graphics.PorterDuff;
6 import android.graphics.drawable.Drawable;
7 import android.support.v4.view.ViewCompat;
8 import android.support.v4.widget.ViewDragHelper;
9 import android.util.AttributeSet;
10 import android.view.MotionEvent;
11 import android.view.View;
12 import android.widget.FrameLayout;
13
14 /**
15 * Created by ${VINCENT} on 2016/11/8.
16 */
17
18 public class DragLayout extends FrameLayout {
19
20 private ViewDragHelper mViewDragHelper;
21 private View mMenuView;
22 private View mMainView;
23 private int mRange;
24 private int mWidth;
25 private int mHeight;
26
27 public DragLayout(Context context) {
28 super(context);
29 init();
30 }
31
32 public DragLayout(Context context, AttributeSet attrs) {
33 super(context, attrs);
34 init();
35 }
36
37 /** 填充完成後調用此方法 */
38 @Override
39 protected void onFinishInflate() {
40 super.onFinishInflate();
41 // 健壯性判斷
42 if (getChildCount() < 2) {
43 throw new IllegalStateException("DrawLayout至少要有兩個子控件");
44 }
45 mMenuView = getChildAt(0);
46 mMainView = getChildAt(1);
47 }
48
49 // step1:創建ViewDragHelper對象
50 private void init() {
51 float sensitivity = 1.0f; //值越大,靈敏度越高
52 mViewDragHelper = ViewDragHelper.create(this, sensitivity, mCallBack);
53 }
54
55 // step2:由ViewDragHelper決定是否攔截事件
56 @Override
57 public boolean onInterceptTouchEvent(MotionEvent ev) {
58 return mViewDragHelper.shouldInterceptTouchEvent(ev);
59 }
60
61 // step3:把觸摸事件交給ViewDragHelper處理
62 @Override
63 public boolean onTouchEvent(MotionEvent event) {
64 mViewDragHelper.processTouchEvent(event);
65 return true; //讓mViewDragHelper持續接收到觸摸事件
66 }
67
68 // step4:處理ViewDragHelper的Callback方法
69 ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {
70
71 // (1)捕獲子控件,返回true表示子控件可以拖動
72 @Override
73 public boolean tryCaptureView(View child, int pointerId) {
74 return true;
75 }
76
77 // (2)子控件顯示的方向(horizontal, vertical)
78 // left: 被拖動控件的將要顯示的位置
79 // dx: 位置的偏移量 = left - 當前的left
80 @Override
81 public int clampViewPositionHorizontal(View child, int left, int dx) {
82 if (child == mMainView) {
83 left = reviseLeft(left);
84 }
85 return left;
86 }
87
88 // (3)返回水平方向拖動的最大范圍mRange,內部會根據返回值計算動畫執行的時間
89 @Override
90 public int getViewHorizontalDragRange(View child) {
91 return mRange;
92 }
93
94 // (4)位置發生改變的回調
95 @Override
96 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
97 // (a) 關聯子控件的滑動
98 if (changedView == mMenuView) {
99 // 側拉菜單界面不變時
100 mMenuView.layout(0, 0, mWidth, mHeight);
101 // 主菜單界面的新位置
102 int newLeft = mMenuView.getLeft() + dx;
103 newLeft = reviseLeft(newLeft);
104 mMainView.layout(newLeft, 0, mWidth + newLeft, mHeight);
105 }
106 // (b) 事件的監聽(打開,拖動,關閉)
107 listenDragStatus();
108 // (c) 事件伴隨的動畫
109 animateChildren();
110 }
111
112 // (5) 拖動結束時回調的方法
113 // xvel:釋放時的回調速度,在這裡向右為正
114 @Override
115 public void onViewReleased(View releasedChild, float xvel, float yvel) {
116 if (xvel > 0) {
117 open();
118 } else if (xvel == 0 && mMainView.getLeft() > mRange / 2) {
119 open();
120 } else {
121 close();
122 }
123 }
124 };
125
126 //============================動畫的定義=====================================
127 /** 估值器:變化值 = 開始值 + (結束值 - 開始值) * 百分比 */
128 public float evaluate(float start, float end, float percent) {
129 return start + (end - start) * percent;
130 }
131
132 protected void animateChildren() {
133 float percent = ((float) mMainView.getLeft()) / mRange;
134
135 // 1.主界面的縮放
136 mMainView.setScaleX(evaluate(1f, 0.8f, percent));
137 mMainView.setScaleY(evaluate(1f, 0.8f, percent));
138 // 2.側拉菜單的縮放
139 mMenuView.setTranslationX((int) evaluate(-mRange, 0, percent)); // 平移
140 mMenuView.setScaleX(evaluate(0.5f, 1.0f, percent));
141 mMenuView.setScaleY(evaluate(0.5f, 1.0f, percent));
142 mMenuView.setAlpha(evaluate(0.5f, 1.0f, percent));
143 // 3.背景圖片:亮度的變化
144 Drawable background = getBackground();
145 if (background != null) {
146 // 過渡的顏色
147 int color = (int)evaluate2(percent, Color.BLACK, Color.TRANSPARENT);
148 background.setColorFilter(color, PorterDuff.Mode.SRC_OVER);
149 }
150 }
151
152 /** 處理顏色漸變的兼容性問題 */
153 public Object evaluate2(float fraction, Object startValue, Object endValue) {
154 int startInt = (Integer) startValue;
155 int startA = (startInt >> 24) & 0xff;
156 int startR = (startInt >> 16) & 0xff;
157 int startG = (startInt >> 8) & 0xff;
158 int startB = startInt & 0xff;
159
160 int endInt = (Integer) endValue;
161 int endA = (endInt >> 24) & 0xff;
162 int endR = (endInt >> 16) & 0xff;
163 int endG = (endInt >> 8) & 0xff;
164 int endB = endInt & 0xff;
165
166 return ((startA + (int)(fraction * (endA - startA))) << 24) |
167 ((startR + (int)(fraction * (endR - startR))) << 16) |
168 ((startG + (int)(fraction * (endG - startG))) << 8) |
169 ((startB + (int)(fraction * (endB - startB))));
170 }
171
172 //============================狀態的監聽begin================================
173 /** 事件的監聽 */
174 protected void listenDragStatus() {
175 int left = mMainView.getLeft();
176 if (left == 0) {
177 mCurrentStatus = DragStatus.CLOSE;
178 } else if (left == mRange) {
179 mCurrentStatus = DragStatus.OPEN;
180 } else {
181 mCurrentStatus = DragStatus.DRAGGING;
182 }
183
184 //當事件發生時,調用監聽器中的方法
185 if (mOnDragListener != null) {
186 if (mCurrentStatus == DragStatus.OPEN) {
187 mOnDragListener.onOpen();
188 } else if (mCurrentStatus == DragStatus.CLOSE) {
189 mOnDragListener.onClose();
190 } else {
191 float percent = ((float) mMainView.getLeft()) / mRange;
192 mOnDragListener.onDragging(percent);
193 }
194 }
195 }
196
197 /** 狀態的定義 */
198 public enum DragStatus {
199 OPEN, CLOSE, DRAGGING
200 }
201
202 /** 當前的狀態 */
203 private DragStatus mCurrentStatus = DragStatus.CLOSE;
204
205 public DragStatus getCurrentStatus() {
206 return mCurrentStatus;
207 }
208
209 /** 定義接口 */
210 public interface OnDragListener {
211 void onOpen();
212 void onClose();
213 void onDragging(float percent);
214 }
215
216 private OnDragListener mOnDragListener;
217
218 /** 提供設置監聽器的set方法 */
219 public void setOnDragListener(OnDragListener onDragListener) {
220 this.mOnDragListener = onDragListener;
221 }
222
223 //============================狀態的監聽end================================
224
225 @Override
226 public void computeScroll() {
227 super.computeScroll();
228 // 若如果沒有移動到正確的位置,需要刷新
229 if (mViewDragHelper.continueSettling(true)) {
230 ViewCompat.postInvalidateOnAnimation(this);
231 }
232 }
233
234 /** 限定主界面的滑動范圍 */
235 protected int reviseLeft(int left) {
236 if (left < 0) {
237 left = 0;
238 } else if (left > mRange) {
239 left = mRange;
240 }
241 return left;
242 }
243
244 /** 控件尺寸發生改變時,回調該方法 */
245 @Override
246 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
247 super.onSizeChanged(w, h, oldw, oldh);
248 // 獲取DrawLayout的寬高
249 mWidth = getMeasuredWidth();
250 mHeight = getMeasuredHeight();
251 // 拖拽的比例
252 mRange = (int) (mWidth * 0.6f);
253 }
254
255 /** 打開側拉菜單 */
256 protected void open() {
257 mViewDragHelper.smoothSlideViewTo(mMainView, mRange, 0);
258 // 刷新界面
259 ViewCompat.postInvalidateOnAnimation(this);
260 }
261
262 /** 關閉側拉菜單 */
263 protected void close() {
264 mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
265 // 刷新界面
266 ViewCompat.postInvalidateOnAnimation(this);
267 }
268
269 /** 側滑菜單是否打開 */
270 public boolean isOpen() {
271 return mCurrentStatus == DragStatus.OPEN;
272 }
273
274 }
2.4 創建MyLinearLayout.java文件,處理側拉與主菜單的沖突事件
1 package com.xiaowu.draglayout.view;
2
3 import android.content.Context;
4 import android.util.AttributeSet;
5 import android.view.MotionEvent;
6 import android.widget.LinearLayout;
7
8 /**
9 * Created by ${VINCENT} on 2016/11/9.
10 */
11
12 public class MyLinearLayout extends LinearLayout {
13
14 private DragLayout mDragLayout;
15
16 public MyLinearLayout(Context context) {
17 super(context);
18 }
19
20 public MyLinearLayout(Context context, AttributeSet attrs) {
21 super(context, attrs);
22 }
23
24 /** 根據它的打開狀態決定是否要攔截事件 */
25 public void setDragLayout(DragLayout dragLayout) {
26 this.mDragLayout = dragLayout;
27 }
28
29 /** 如果側滑菜單打開了,禁止主菜單的列表滑動 */
30 @Override
31 public boolean onInterceptTouchEvent(MotionEvent ev) {
32 if (mDragLayout.isOpen()) {
33 return true;
34 }
35 return super.onInterceptTouchEvent(ev);
36 }
37
38 /** 如果側滑菜單打開了,消費主菜單的觸摸事件,禁止通過滑動主菜單使側拉菜單的列表滑動 */
39 @Override
40 public boolean onTouchEvent(MotionEvent event) {
41 if (mDragLayout.isOpen()) {
42 return true;
43 }
44 return super.onTouchEvent(event);
45 }
46 }
2.5 接下來是MainActivity的代碼實現
1 package com.xiaowu.draglayout;
2
3 import android.graphics.Color;
4 import android.support.v7.app.AppCompatActivity;
5 import android.os.Bundle;
6 import android.view.View;
7 import android.view.ViewGroup;
8 import android.view.Window;
9 import android.widget.ArrayAdapter;
10 import android.widget.ImageView;
11 import android.widget.ListView;
12 import android.widget.TextView;
13 import android.widget.Toast;
14
15 import com.xiaowu.draglayout.view.DragLayout;
16 import com.xiaowu.draglayout.view.MyLinearLayout;
17
18 public class MainActivity extends AppCompatActivity {
19
20 private ImageView mIvHeader;
21 private MyLinearLayout mMyLinearLayout;
22 private DragLayout mDragLayout;
23
24 @Override
25 protected void onCreate(Bundle savedInstanceState) {
26 super.onCreate(savedInstanceState);
27 requestWindowFeature(Window.FEATURE_NO_TITLE);
28
29 setContentView(R.layout.activity_main);
30 mIvHeader = (ImageView) findViewById(R.id.iv_header);
31
32 initDragLayout();
33 mMyLinearLayout = (MyLinearLayout) findViewById(R.id.my_ll);
34 // 根據打開的狀態決定是否攔截事件
35 mMyLinearLayout.setDragLayout(mDragLayout);
36
37 initListView();
38 }
39
40 private void initListView() {
41 ListView lvMenu = (ListView) findViewById(R.id.lv_menu);
42 ListView lvMain = (ListView) findViewById(R.id.lv_main);
43
44 lvMenu.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
45 Constant.MENUS){
46 @Override
47 public View getView(int position, View convertView, ViewGroup parent) {
48 TextView view = (TextView) super.getView(position, convertView, parent);
49 view.setTextSize(dp2px(16));
50 view.setTextColor(Color.WHITE);
51 return view;
52 }
53 });
54
55 lvMain.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
56 Constant.LIST_DATAS));
57
58 }
59
60 private void initDragLayout() {
61 mDragLayout = (DragLayout) findViewById(R.id.drag_layout);
62 mDragLayout.setOnDragListener(new DragLayout.OnDragListener() {
63 @Override
64 public void onOpen() {
65 showToast("打開");
66 }
67
68 @Override
69 public void onClose() {
70 showToast("關閉");
71 }
72
73 @Override
74 public void onDragging(float percent) {
75 mIvHeader.setAlpha(1 - percent );
76 }
77 });
78 }
79
80 /** toast使用單例模式,可以隨狀態刷新 */
81 private Toast mToast;
82
83 public void showToast(String msg) {
84 if (mToast == null) {
85 mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
86 }
87 mToast.setText(msg);
88 mToast.show();
89 }
90
91 public int dp2px(int dp) {
92 float density = this.getResources().getDisplayMetrics().density;
93 return (int) (dp * density + 0.5f);
94 }
95
96 }
2.6 布局文件的最終完善
1 <com.xiaowu.draglayout.view.DragLayout 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 android:id="@+id/drag_layout" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:background="@drawable/bg"> 7 8 <!-- 側拉菜單界面 --> 9 <LinearLayout 10 android:layout_width="match_parent" 11 android:layout_height="match_parent" 12 android:orientation="vertical" 13 android:gravity="center_vertical" 14 android:padding="15dp" > 15 16 <ImageView 17 android:layout_width="40dp" 18 android:layout_height="40dp" 19 android:background="@drawable/head"/> 20 21 <ListView 22 android:id="@+id/lv_menu" 23 android:layout_width="match_parent" 24 android:layout_height="match_parent" 25 android:layout_marginTop="10dp" /> 26 27 </LinearLayout> 28 29 <!-- 主菜單界面 --> 30 <com.xiaowu.draglayout.view.MyLinearLayout 31 android:id="@+id/my_ll" 32 android:layout_width="match_parent" 33 android:layout_height="match_parent" 34 android:orientation="vertical" 35 android:gravity="center" 36 android:background="#fff"> 37 <RelativeLayout 38 android:layout_width="match_parent" 39 android:layout_height="45dp" 40 android:background="#18B4ED" > 41 42 <ImageView 43 android:id="@+id/iv_header" 44 android:layout_width="40dp" 45 android:layout_height="40dp" 46 android:layout_centerVertical="true" 47 android:layout_marginLeft="10dp" 48 android:background="@drawable/head" /> 49 </RelativeLayout> 50 51 <ListView 52 android:id="@+id/lv_main" 53 android:layout_width="match_parent" 54 android:layout_height="match_parent" 55 android:layout_weight="1" /> 56 57 </com.xiaowu.draglayout.view.MyLinearLayout> 58 59 </com.xiaowu.draglayout.view.DragLayout>
2.7 Constant靜態類可以自己定義,不過我還是善良的貼了出來
package com.xiaowu.draglayout;
public class Constant {
/** 菜單列表數據 */
public static final String[] MENUS = new String[] {
"紙杯蛋糕[Cupcake]",
"甜甜圈[Donut]",
"閃電泡芙[Eclair]",
"凍酸奶[Froyo]",
"姜餅[Gingerbread]",
"蜂巢[Honeycomb]",
"冰淇淋三明治[Ice Cream Sandwich]",
"果凍豆[Jelly Bean]",
"奇巧[KitKat]",
"棒棒糖[Lollipop]",
"姜餅[Gingerbread]",
"蜂巢[Honeycomb]",
"冰淇淋三明治[Ice Cream Sandwich]",
"果凍豆[Jelly Bean]",
"奇巧[KitKat]",
"棒棒糖[Lollipop]",
"棉花糖[Marshmallow]"
};
/** 列表數據1 */
public static final String[] LIST_DATAS = {
"API1--1.0 [沒有開發代號]",
"API2--1.1 Petit Four",
"API3--1.5 Cupcake",
"API4--1.6 Donut",
"API5--2.0 Eclair",
"API6--2.0.1 Eclair",
"API7--2.1 Eclair",
"API8--2.2 - 2.2.3 Froyo",
"API9--2.3 - 2.3.2 Gingerbread",
"API10--2.3.3-2.3.7 Gingerbread",
"API11--3.0 Honeycomb",
"API12--3.1 Honeycomb",
"API13--3.2 Honeycomb",
"API14--4.0 - 4.0.2 Ice Cream Sandwich",
"API15--4.0.3 - 4.0.4 Ice Cream Sandwich",
"API16--4.1 Jelly Bean",
"API17--4.2 Jelly Bean",
"API18--4.3 Jelly Bean",
"API19--4.4 KitKat",
"API20--4.4W",
"API21--5.0 Lollipop",
"API22--5.1 Lollipop",
"API23--6.0 Marshmallow"
};
}
三、一些可以借鑒的東西
*比如使用Toast的時候可以采用單例模式,使得Toast可以隨時改變,而不會產生停頓延遲的問題(頂部效果圖)
1 /** toast使用單例模式,可以隨狀態刷新 */
2 private Toast mToast;
3
4 public void showToast(String msg) {
5 if (mToast == null) {
6 mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
7 }
8 mToast.setText(msg);
9 mToast.show();
10 }
四、提供給博友我的源代碼
**下載鏈接:http://pan.baidu.com/s/1o8k4cZo 密碼:m0fl
安卓應用反編譯(二)-APK包反編譯淺析
安卓應用反編譯(二)-APK包反編譯淺析 第二章 APK包反編譯 被編譯器處理過的代碼和資源已經打包成了APK,有的甚至被轉化成了二進制文件。但是我們也有一些方法,把這些
Android學習筆記(25):帶動畫效果的View切換ViewAnimator及其子類
Android學習筆記(25):帶動畫效果的View切換ViewAnimator及其子類 ViewAnimator可以實現帶動畫效果的View切換,其派生的子類是一些帶動
Android 用Canvas畫textview、bitmap、矩形(裁剪)、橢圓、線、點、弧
Android 用Canvas畫textview、bitmap、矩形(裁剪)、橢圓、線、點、弧 初始化對象 private Paint mPaint;//畫筆 pri
Android動態部署五:如何從插件apk中啟動Service
Android動態部署五:如何從插件apk中啟動Service 經過前面幾篇文章的分析,我們了解到了Google原生是如何拆分apk的,並且我們自己可以通過解析manif