編輯:關於Android編程
一個自定義ViewGroup的工具類,它提供了許多有用的方法和狀態允許用戶去拖拽和繪制子View在自定義ViewGroup中的軌跡和位置。
ViewDragHelper的創建;
ViewDragHelper可以使用靜態方法創建一個實例:
ViewDragHelper.create(ViewGroup forParent,int sensitiveity,ViewDragHelper.Callback cb)
在自定義ViewGroup中,ViewDragHelper可以幫助我們來分析手勢和處理拖動。
@Override public boolean onTouchEvent(MotionEvent event) { try { //處理觸摸事件 mDragHelper.processTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } //返回true, return true; }
使用ViewDragHelper來動態移動自定義ViewGroup中的控件:
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) // Animate the view child to the given (left, top) position. // 返回true 代表還沒有移動到指定的位置,需要刷新界面,繼續移動 // 返回false 就停止工作
ViewDragHelper.Callback集成了許多可覆寫的方法,所有移動的控制在ViewDragHelper.Callback裡面來實現。
是否可以捕捉ViewGroup中的子組件:
public boolean tryCaptureView(View child, int pointerId) { //返回true,就代表著可對該子組件處理滑動事件。否則就不會處理。 return true; //只對特定的組件捕捉 return speChild == child; }
clampViewPositionHorizontal[Vertical]:
處理子組件在水平或者豎直方向的滑動限制,在這個方法內部做子組件的邊界處理,就是確保子組件不會滑過界。
例如在豎直方向進行滑動時,一般先獲取控件可滑動到的頂端Y值和底端Y值,再進行一個取值
@Override public int clampViewPositionVertical(View child, int top, int dy) { //手指觸摸移動時實時回調, top表示要到的y位置 int topBound = ...; int bottomBound = ...; return Math.min(Math.max(topBound, top), bottomBound); }
onViewPositionChanged
當前拖動的子組件位置變化時調用的方法。一般在該方法裡調整其他子組件的位置。
@Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { //changedView為當前位置發生改變的View,left,top分別為該View的left和top坐標 }
onViewReleased
當手指釋放的時候會調用的方法。在這個方法裡實現松開時的滑動效果。
@Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //released 為釋放的View,xvel,yvel分別為該手指離開時滑動和豎直滑動的速度 }
getViewVertical[Horizontal]DragRangeHorizontal
@Override public int getViewVerticalDragRange(View child) { //返回當前捕捉的child子組件的豎直滑動范圍 return ...; }
……
自定義可下拉展示內容的的ViewGroup
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="一自定義viewgroupdragdownlayout">一、自定義ViewGroup:DragDownLayout
import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; /** * Created by cxm on 2016/8/22. */ public class DragDownLayout extends ViewGroup { private ViewDragHelper dragHelper; //下拉的控件和內容控件 private View mDragbar, mContentView; private int dragRange; //對外的接口 private OnOpenListener mOnOpenListener; private OnCloseListener mOnCloseListener; private boolean isOpen;//內容是否打開著 public DragDownLayout(Context context) { super(context); init(); } public DragDownLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DragDownLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public DragDownLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { dragHelper = ViewDragHelper.create(this, mCallback); } @Override protected void onLayout(boolean b, int l, int t, int r, int bo) { mDragbar.layout(0, 0, getWidth(), mDragbar.getMeasuredHeight()); mContentView.layout(0, -mContentView.getMeasuredHeight(), getWidth(), 0); } @Override public boolean onInterceptHoverEvent(MotionEvent event) { final int action = MotionEventCompat.getActionMasked(event); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { dragHelper.cancel(); return false; } return dragHelper.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { dragHelper.processTouchEvent(event); return true; } private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return child == mDragbar; } @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { mContentView.layout(0, top-mContentView.getHeight(), getWidth(), top ); } @Override public int clampViewPositionVertical(View child, int top, int dy) { int topBound = getPaddingTop(); int bottomBound = getHeight() - mDragbar.getHeight(); return Math.min(Math.max(topBound, top), bottomBound); } @Override public int getViewVerticalDragRange(View child) { dragRange = mContentView.getHeight(); return dragRange; } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //下拉的位置大於內容控件的1/2時,則向下滑動到底 if (mContentView.getBottom()>mContentView.getHeight()/2) { smoothToBottom(); } else if (mContentView.getBottom()<=mContentView.getHeight()/2) { smoothToTop(); } invalidate(); } }; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mDragbar = getChildAt(0); mContentView = getChildAt(1); LayoutParams contPar = mDragbar.getLayoutParams(); //父容器給內容組件指定大小 int heighContentSpec = MeasureSpec.makeMeasureSpec(contPar.height, MeasureSpec.EXACTLY); //測量子組件,內容組件寬隨父布局 mDragbar.measure(widthMeasureSpec, heighContentSpec); LayoutParams delPar = mContentView.getLayoutParams(); int heightDelSpec = MeasureSpec.makeMeasureSpec(delPar.height, MeasureSpec.EXACTLY); mContentView.measure(widthMeasureSpec, heightDelSpec); //父布局設置最終的寬和高,寬沿用自己測量自己的widthMeasureSpec,高度使用內容布局的高度 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), contPar.height+delPar.height); } private void smoothToTop() { if (dragHelper.smoothSlideViewTo(mDragbar, getPaddingLeft(), getPaddingTop())) { ViewCompat.postInvalidateOnAnimation(this); isOpen = false; if(mOnCloseListener!=null) mOnCloseListener.close(); } } private void smoothToBottom() { if (dragHelper.smoothSlideViewTo(mDragbar, getPaddingLeft(), getHeight()-getPaddingBottom()-mDragbar.getHeight())) { ViewCompat.postInvalidateOnAnimation(this); isOpen = true; if(mOnOpenListener!=null) mOnOpenListener.open(); } } @Override public void computeScroll() { super.computeScroll(); if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } public boolean isOpen(){ return isOpen; } public void openContent(){ if(!isOpen) smoothToBottom(); } public void closeContent(){ if(isOpen) smoothToTop(); } public interface OnOpenListener{ void open(); } public interface OnCloseListener{ void close(); } public void setOnOpenListener(OnOpenListener mOnOpenListener) { this.mOnOpenListener = mOnOpenListener; } public void setOnCloseListener(OnCloseListener mOnCloseListener) { this.mOnCloseListener = mOnCloseListener; } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 2) { throw new IllegalStateException("Just contain two Views/ViewGroups "); } } }
使用時要標明拖拽控件和內容控件的高度。
mDragDownLayout = (DragDownLayout) findViewById(R.id.myDragDownLayout); mDragDownLayout.setOnOpenListener(new DragDownLayout.OnOpenListener() { @Override public void open() { Toast.makeText(MainActivity.this,"open",Toast.LENGTH_SHORT).show(); } });
DragDownLayout
上一篇小案例,完成了一個普通的通知,點擊通知啟動了一個活動。但是那裡的通知沒有加入些“靓點”,這一篇就給它加入自定義的布局,完成自定義的通知。應用
這是一個一言不合就手撸一個自定義View的任性時代,因此最近一段時間一直在學習自定義View相關的知識,也看了很多與此相關的博客,有句話叫做不要重復造輪子,別人寫好的直接
廣播作為android的四大組件之一,適用的地方還是很多,多用來特定條件情況下的通知。例如,開機,鬧鈴,電池電量過低等等。但還可以自定義廣播,用來兩個應用程序的通知。曾經
在之前的Android超精准計步器開發-Dylan計步中的首頁用到了一個自定義控件,和QQ運動的界面有點類似,還有動畫效果,下面就來講一下這個View是如何繪制的。1.先