這倆周在做發(fā)表的相冊改版敢伸,今天總結(jié)一下其中用到的一些技術(shù)點(diǎn)
1袜匿、android:clipToPadding屬性的妙用
屬性的解釋:
Defines?whether?the?ViewGroup?will?clip?its?drawing?surface?so?as?to?exclude?the?padding?area.
定義了是否允許ViewGroup在padding中繪制醋拧。
該屬性的默認(rèn)值是true,即不允許艘绍。
使用場景:
1、設(shè)置ListView(或GridView等)首行或尾行距離頂部或底部有一段距離(padding)跟继,且在滑動時padding部分仍看到ListView內(nèi)容的效果密浑。
例:
android:layout_width="match_parent"
android:layout_height=“match_parent"
android:clipToPadding="false"
android:paddingTop="70dip"
android:paddingBottom="70dip"
/>
若不設(shè)置clipPadding為false,我們會直接看到ListView頭部和尾部占有70dp的padding,且在滑動過程中padding始終存在蛙婴,且在padding部分看不到ListView的內(nèi)容。
若設(shè)置clipPadding為false,滑動過程中在padding部分可以看到ListView的內(nèi)容,且有滑動到頂部才出現(xiàn)paddingTop 70dp,滑動到底部才出現(xiàn)paddingBottom 70dp的效果尔破。
以前為了達(dá)到這個效果街图,解決方法可能是在首行或尾行加上一個隱藏的View,或者是加HeaderView\FooterView來實現(xiàn)背传。比較麻煩。
2台夺、與ViewPager結(jié)合實現(xiàn)畫廊效果
畫廊效果:每次滑動只滑動一頁+ 滑動item居中 + 一屏顯示多個item
//緩存?3屏 ?左右拖動可以看到前后的圖片mViewPager.setOffscreenPageLimit(3);
//設(shè)置item之間的間距
//int?pageMargin?=?ScreenTools.instance(this).dip2px(-15);
//mViewPager.setPageMargin(pageMargin);
intpadding?=?ScreenTools.instance(this).dip2px(40);
mViewPager.setPadding(padding,0,padding,0);
mViewPager.setClipToPadding(false);
效果圖如下:
3径玖、網(wǎng)上有用android:clipChildren實現(xiàn)畫廊效果http://www.trinea.cn/android/viewpager-multi-fragment-effect/
android:clipChildren
屬性解釋:
Defines whether a child is limited to draw inside of its bounds or not.
用來定義他的子控件是否要在他應(yīng)有的邊界內(nèi)進(jìn)行繪制。
默認(rèn)值為true,即不允許進(jìn)行擴(kuò)展繪制颤介。
2梳星、ViewDragHelper
ViewDragHelper 是v4包中提供的一個用于解決界面控件拖拽移動問題的類,用這個類編寫自定義的ViewGroup,可很大程度上簡化了手勢處理過于復(fù)雜的問題滚朵。自己去處理onInterceptTouchEvent和onTouchEvent這兩個方法真的是一件很不容易的事
ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
of useful operations and state tracking for allowing a user to drag and reposition
views within their parent ViewGroup.
ViewDragHepler基本用法:
1冤灾、獲取ViewDragHelper的實例
ViewDragHelper.create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb);
參數(shù)1: 一個ViewGroup, 也就是ViewDragHelper將要用來拖拽誰下面的子view
參數(shù)2:靈敏度辕近,一般設(shè)置為1.0f就行
參數(shù)3:一個回調(diào)韵吨,用來處理拖動到位置
2、繼承ViewDragHelper.Callback類移宅,該類有個抽象方法:tryCaptureView(View view, int pointerId) 表示嘗試捕獲子view归粉,這里一定要返回true, 返回true表示允許漏峰。
3糠悼、重寫兩個方法Callback中int?clampViewPositionHorizontal(View child, int left, int dx)和intclampViewPositionVertical(View child, int left, int dx) 這兩個方法分別用來處理x方向和y方向的拖動的,返回值該child現(xiàn)在的位置浅乔。使用時要具體處理例如防止view超出邊界等倔喂。
4、重寫了Callback中的onViewReleased,處理放手后的操作靖苇,例根據(jù)滑動方向及滑動距離等調(diào)用settleCapturedViewAt方法回到指定的位置席噩,settleCapturedViewAt后要調(diào)用invalidate,因為其內(nèi)部使用的是mScroller.startScroll,所以別忘了需要invalidate()以及結(jié)合computeScroll方法一起贤壁。
5悼枢、重寫Callback中g(shù)etViewHorizontalDragRange和getViewVerticalDragRange,只有這兩個方法返回大于0view的onClick事件才能執(zhí)行芯砸。(如果子View不消耗事件萧芙,那么整個手勢(DOWN-MOVE*-UP)都是直接進(jìn)入onTouchEvent给梅,在onTouchEvent的DOWN的時候就確定了captureView假丧。如果消耗事件,那么就會先走onInterceptTouchEvent方法动羽,判斷是否可以捕獲包帚,而在判斷的過程中會去判斷另外兩個回調(diào)的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有這兩個方法返回大于0的值才能正常的捕獲运吓。)
6渴邦、重寫ViewGroup的onInterceptTouchEvent(MotionEvent ev)用來攔截事件
7疯趟、重寫ViewGroup的onTouchEvent(MotionEvent event) 在這里面只要做兩件事:mDragHelper.processTouchEvent(event);處理攔截到的事件,這個方法會在返回前分發(fā)事件谋梭;return true 表示消費(fèi)了事件信峻。
簡單的幾步就可以實現(xiàn)一個可以任意拖動到view了。
例子:
相片選擇模塊中底部的上下拖動抽屜效果及畫廊中圖片的拖動上滑刪除功能都是用ViewDragHelper來實現(xiàn)的
效果圖:畫廊拖動上滑刪除圖片
效果圖:底部上拉抽屜
3瓮床、PopupWindow?setBackgroundDrawbale()
PopupWindow大家經(jīng)常用盹舞,類似這樣
效果:點(diǎn)擊Popwindow外部區(qū)域可讓其消失
mAlbumPopupWindow=newPopupWindow(contentView,ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT,true);
mAlbumPopupWindow.setBackgroundDrawable(newColorDrawable(Color.TRANSPARENT));//加上這行的效果是點(diǎn)popupwindow的外邊也能dismiss
mAlbumPopupWindow.setOutsideTouchable(true);//外部區(qū)域可點(diǎn)擊
mAlbumPopupWindow.setTouchable(true);//window?可點(diǎn)擊
mAlbumPopupWindow.setFocusable(true);//獲得焦點(diǎn)?內(nèi)部可點(diǎn)擊
有個點(diǎn):PopupWindow setBackgroundDrawbale() 有什么作用?
為什么去掉這行隘庄,點(diǎn)擊外部區(qū)域踢步,Popupwindow就不會消失呢?
解惑:
如果有背景mBackground丑掺,則會在contentView外面包一層PopupViewContainer之后作為mPopupView获印,如果沒有背景,則直接用contentView作為mPopupView街州。
而這個PopupViewContainer是一個內(nèi)部私有類兼丰,它繼承了FrameLayout,在其中重寫了Key和Touch事件的分發(fā)處理唆缴。如果不設(shè)背景地粪,由于PopupView本身并沒有重寫Key和Touch事件的處理,所以如果沒有包這個外層容器類琐谤,點(diǎn)擊Back鍵或者外部區(qū)域是不會導(dǎo)致彈框消失的蟆技。
見源碼:
/**
*
Prepare?the?popup?by?embedding?in?into?a?new?ViewGroup?if?the
*?background?drawable?is?not?null.?If?embedding?is?required,?the?layout
*?parameters'?height?is?modified?to?take?into?account?the?background's
*?padding.
*
*@parampthe?layout?parameters?of?the?popup's?content?view
*/
private?voidpreparePopup(WindowManager.LayoutParams?p)?{
if(mContentView==null||mContext==null||mWindowManager==null)?{
throw?newIllegalStateException("You?must?specify?a?valid?content?view?by?"
+"calling?setContentView()?before?attempting?to?show?the?popup.");
}
if(mBackground!=null)?{
finalViewGroup.LayoutParams?layoutParams?=mContentView.getLayoutParams();
intheight?=?ViewGroup.LayoutParams.MATCH_PARENT;
if(layoutParams?!=null&&
layoutParams.height==?ViewGroup.LayoutParams.WRAP_CONTENT)?{
height?=?ViewGroup.LayoutParams.WRAP_CONTENT;
}
//?when?a?background?is?available,?we?embed?the?content?view
//?within?another?view?that?owns?the?background?drawable
PopupViewContainer?popupViewContainer?=newPopupViewContainer(mContext);
PopupViewContainer.LayoutParams?listParams?=newPopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,height
);
popupViewContainer.setBackground(mBackground);
popupViewContainer.addView(mContentView,listParams);
mPopupView=?popupViewContainer;
}else{
mPopupView=mContentView;
}
mPopupView.setElevation(mElevation);
mPopupViewInitialLayoutDirectionInherited=
(mPopupView.getRawLayoutDirection()?==?View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth=?p.width;
mPopupHeight=?p.height;
}
private?classPopupViewContainerextendsFrameLayout?{
private?static?finalStringTAG="PopupWindow.PopupViewContainer";
publicPopupViewContainer(Context?context)?{
super(context);
}
@Override
protected?int[]?onCreateDrawableState(intextraSpace)?{
if(mAboveAnchor)?{
//?1?more?needed?for?the?above?anchor?state
final?int[]?drawableState?=super.onCreateDrawableState(extraSpace?+1);
View.mergeDrawableStates(drawableState,ABOVE_ANCHOR_STATE_SET);
returndrawableState;
}else{
return?super.onCreateDrawableState(extraSpace);
}
}
@Override
public?booleandispatchKeyEvent(KeyEvent?event)?{
if(event.getKeyCode()?==?KeyEvent.KEYCODE_BACK)?{
if(getKeyDispatcherState()?==null)?{
return?super.dispatchKeyEvent(event);
}
if(event.getAction()?==?KeyEvent.ACTION_DOWN
&&?event.getRepeatCount()?==0)?{
KeyEvent.DispatcherState?state?=?getKeyDispatcherState();
if(state?!=null)?{
state.startTracking(event,this);
}
return?true;
}else?if(event.getAction()?==?KeyEvent.ACTION_UP)?{
KeyEvent.DispatcherState?state?=?getKeyDispatcherState();
if(state?!=null&&?state.isTracking(event)?&&?!event.isCanceled())?{
dismiss();
return?true;
}
}
return?super.dispatchKeyEvent(event);
}else{
return?super.dispatchKeyEvent(event);
}
}
@Override
public?booleandispatchTouchEvent(MotionEvent?ev)?{
if(mTouchInterceptor!=null&&mTouchInterceptor.onTouch(this,ev))?{
return?true;
}
return?super.dispatchTouchEvent(ev);
}
@Override
public?booleanonTouchEvent(MotionEvent?event)?{
final?intx?=?(int)?event.getX();
final?inty?=?(int)?event.getY();
if((event.getAction()?==?MotionEvent.ACTION_DOWN)
&&?((x?<0)?||?(x?>=?getWidth())?||?(y?<0)?||?(y?>=?getHeight())))?{
dismiss();
return?true;
}else?if(event.getAction()?==?MotionEvent.ACTION_OUTSIDE)?{
dismiss();
return?true;
}else{
return?super.onTouchEvent(event);
}
}
@Override
public?voidsendAccessibilityEvent(inteventType)?{
//?clinets?are?interested?in?the?content?not?the?container,?make?it?event?source
if(mContentView!=null)?{
mContentView.sendAccessibilityEvent(eventType);
}else{
super.sendAccessibilityEvent(eventType);
}
}
}
}