編輯:關於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,有的甚至被轉化成了二進制文件。但是我們也有一些方法,把這些
Android學習筆記(25):帶動畫效果的View切換ViewAnimator及其子類 ViewAnimator可以實現帶動畫效果的View切換,其派生的子類是一些帶動
Android 用Canvas畫textview、bitmap、矩形(裁剪)、橢圓、線、點、弧 初始化對象 private Paint mPaint;//畫筆 pri
Android動態部署五:如何從插件apk中啟動Service 經過前面幾篇文章的分析,我們了解到了Google原生是如何拆分apk的,並且我們自己可以通過解析manif