編輯:關於Android編程
先看 SwipeLayout的效果圖
圖太多了,我這只上傳一張,想看 listview和GridView效果的,和想看源碼的 —> GitHub
怎麼實現後面說,先說會廢話。
最近整理以前的項目,那時有一個這樣的需求,要在ExpandableListView的ChildView上實現 編輯和刪除的側滑菜單。我當時並沒有用別人的框架,實現出來大概是這個樣子。
滑動事件完好,沒有沖突,子view可以獲得點擊事件。
實現起來非常簡單,大概分為三步:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPqLZoaLV4rj2SXRlbbK8vtayydPDSG9yaXpvbnRhbFNjcm9sbFZpZXfAtNf3zqrX7s3isuOyvL7Wo6i21KOsvs3Kx8v8o6zL/L/J0tS64c/yufa2r6Opo6zXotLiIEhvcml6b250YWxTY3JvbGxWaWV3vMyz0LXEysdGcmFtZUxheW91dKOs0uLOttfF1rvE3NPQ0ru49tfTsry+1qGj1NrV4rj219OyvL7WwO+jrLDat8XBvbj219PX07K8vtajrGxlZnTTw9Paz9TKvsTayN2jrHJpZ2h0z9TKvrLLtaWhozwvcD4NCjxwPqLaoaKz9cq8u6/W0LvxyKHGwcS7v+22yDwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
DisplayMetrics dm = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
mScreentWidth = dm.widthPixels;
在Adapter的getChildView方法中設置leftView的寬度為屏幕寬度
// 設置leftView的大小為屏幕寬度,這樣右邊的rightView就正好被擠出屏幕外
holder.leftview= convertView.findViewById(R.id.left);
LayoutParams lp = holder.leftview.getLayoutParams();
lp.width = mScreentWidth;
③、getChildView方法中給convertView設置Touch監聽事件
convertView.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
if (view != null) {
//有view被滑開了,點擊其他childview時,用於還原
ViewHolder viewHolder = (ViewHolder) view.getTag();
viewHolder.hsv.smoothScrollTo(0, 0);
}
break;
case MotionEvent.ACTION_UP:
ViewHolder viewHolder = (ViewHolder) v.getTag();
view = v;
// 獲得HorizontalScrollView滑動的水平方向值.
int scrollX = viewHolder.hsv.getScrollX();
int rightW = viewHolder.rightView.getWidth();
if (scrollX < rightW / 4)
{ //滑動距離小於右邊布局的1/4收縮
viewHolder.hsv.smoothScrollTo(0, 0);
}else
{ //展開
viewHolder.hSView.smoothScrollTo(rightW, 0);
}
break;
}
return true;
}
});
// 刪除一條後更新狀態
if (holder.hsv.getScrollX() != 0) {
holder.hsv.scrollTo(0, 0);
}
簡單說一下,HorizontalScrollView在dispatchTouchEvent的時候,如果發現時橫向滑動就把事件交給onTouchEvent處理,而這個onTouchEvent方法是來自view的(viewGroup也是調用view的該方法),在view的dispatchTouchEvent中,是順序調用OnTouchListener和onTouchEvent的。我們這setOnTouchListener中自己處理了滑動事件,並且返回true,就消耗掉了事件,不會再調用onTouchEvent。
開始說主題,假設都對 view和viewGroup的事件分發機制 、自定義viewGroup 的流程 和 Scroller 都有了初步的了解。如果沒有,我們就假設有!
我是繼承LinearLayout實現的,為什麼不繼承HorizontalScrollView或是ViewGroup?
HorizontalScrollView其實就是一個實現滾動功能的FrameLayout,view的onMeasure和onLayout是層層實現的,我不能在HorizontalScrollView的onLayout方法中對其子view的子view直接設置為屏幕寬度。
繼承ViewGroup就要自己測量和擺放子view,開什麼玩笑,我這麼懶得人。
為了節省大家寶貴的時間,下面只說重點。
為什麼有事件沖突呢? 我們知道listview是上下滑動的,而我們的這個側滑布局要左右滑動。當我們屏幕上橫向滑動時,只要稍微斜了一點,那麼listview就認為要上下滑動,它就把滑動事件攔截了自己交給自己的onTouchEvent處理。根本都分發不到SwipeLayout的局部中。產生效果:側滑出來一點劃不動了,卡住了…
然而google早已看穿了一切,他們給我們提供了這個方法。
requestDisallowInterceptTouchEvent(true);
看過源碼的伙伴肯定知道,這其實就是設置一個標志位。當傳入true時,駁回父view的中斷請求。( 就是父view不能中斷事件必須分發到子view。)
那麼事件就由我們處理了,因為listview需要上下滑動事件,而SwipeLayout需要左右滑動事件,剛好各取所需,復寫dispatchTouchEvent(MotionEvent ev)方法來實現各取所需。
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
/**
* 不允許父view對觸摸事件的攔截
*/
disallowParentsInterceptTouchEvent(getParent());
startX = ev.getX();
startY = ev.getY();
isHorizontalMove =false;
break;
case MotionEvent.ACTION_MOVE:
if(!isHorizontalMove){
curX = ev.getX();
curY = ev.getY();
float dx = curX - startX;
float dy = curY - startY;
/**
* 認為發生了滑動
*/
if(dx*dx+dy*dy > mTouchSlop*mTouchSlop){
/**
* 垂直滑動
*/
if (Math.abs(dy) > Math.abs(dx)){
/**
* 允許父view對觸摸事件攔截,讓其他view去處理事件
*/
allowParentsInterceptTouchEvent(getParent());
/**
* 垂直滾動復原所有item
*/
shrinkAllView();
}else{
/**
* 水平滑動,攔截來自己處理
*/
isHorizontalMove = true;
/**
* 為了在onTouchEvent的Move事件中第一次模擬滑動距離不要太大,
* 記錄上一次發生move的位置
*/
lastX = curX;
}
}
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
在ACTION_DOWN中 駁回父view攔截,那麼我們就可以開心的在ACTION_MOVE對事件進行處理(先判斷是否發生了滑動,再判斷是 上下 還是 左右,是上下就取消對父類的駁回,讓listview去處理事件,同時讓所有劃開的SwipeLayout關閉。是左右滑動就 中斷繼續往下層分發,攔截到自己的onTouchEvent做事件處理)。
中斷分發:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(isHorizontalMove){
/**
* 發生水平滑動,把事件中斷到本層onTouchEvent中處理
*/
return true;
}
return super.onInterceptTouchEvent(ev);
}
我這側滑菜單裡是兩個imageView默認是不可clickable的,所以就算不攔截最後也會執行我的onTouchEvent處理。但是如果是Button,ImageButton等默認是可點擊的,那麼事件就傳不上來了。
因為我們不知道是哪個父view對事件進行了攔截,所以要循環遞歸設置標志位(為了更好的兼容性,反正我這是listview攔截了)。
/**
* 因為不知道是父view那一層會攔截觸摸事件,所以遞歸向上設置標志位
* 直到頂層view,就直接返回
*/
private void disallowParentsInterceptTouchEvent(ViewParent parent) {
if (null == parent) {
return;
}
parent.requestDisallowInterceptTouchEvent(true);
disallowParentsInterceptTouchEvent(parent.getParent());
}
private void allowParentsInterceptTouchEvent(ViewParent parent) {
if (null == parent) {
return;
}
parent.requestDisallowInterceptTouchEvent(false);
allowParentsInterceptTouchEvent(parent.getParent());
}
當標志位表明是橫向滑動時,我們需要在ACTION_MOVE裡面模擬滾動。觸發一次ACTION_MOVE就讓內容滾動一點點,實現效果就是內容跟隨手指移動。
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
if(isHorizontalMove){
curX = ev.getX();
float dX = curX-lastX;
/**
* 不斷更新lastX的位置,用於模擬滑動
*/
lastX = curX;
/**
* 滑動的距離與實際相反,因為滾動的時候移動的是內容,不是view
*/
int disX = getScrollX() + (int)(-dX);
/**
* 手指向右移動
*/
if(disX<0){
/**
* 如果菜單收縮,防止越界(越界後ACTION_UP又會滾動回來,但還是不越界的好)
* 如果菜單展開,,我們希望迅速關閉菜單,不需要模擬滾動
*/
scrollTo(0, 0);
}
/**
* 手指向左移動,如果累加的移動距離已經大於menu的寬度,就讓menu顯示出來。
* 如果移動距離還不到,就模擬滾動
*/
else if(disX>rightViewWidth){
scrollTo(rightViewWidth,0);
}
else{
scrollTo(disX, 0);
}
}
break;
case MotionEvent.ACTION_UP:
float endX = ev.getX();
float dis =endX -startX;
/**
* 手指向左滑動,模擬展開
*/
if(dis<0){
SimulateScroll(EXPAND);
}
/**
* 手指向右滑動,模擬關閉
*/
else{
SimulateScroll(SHRINK);
}
default:
break;
}
return true;
}
先說一下這個lastX,這個坐標是onInterceptTouchEvent中判斷為橫向移動後的最近坐標。(事件不能被消費掉,但是會隨著時間消失,我們可以在最內層到最外層的所有view的onTouchEvent中對一個事件進行處理,全部返回false。但是這裡是 在onInterceptTouchEvent中已經觸發了11個(大概)ACTION_MOVE事件,然後onTouchEvent才進行處理。簡單的說就是我滑動的前一段距離拿去做判斷了,判斷好了才跟這手指移動。)
那這麼辦呢?要麼
float dX = curX-lastX;
滑動順暢,不足的距離由最後scroller模擬滾動補回。
float dX = curX-startX;
剛發生移動那一下移動約10個ACTION_MOVE事件的距離,效果不好。
給不了解的惡補一下概念:
getX() 和getY()是view的內部坐標,大小補回超過長寬。
getScrollX()
獲取的是view左上角到內容左上角的距離。至於正負表示方向。
Positive numbers will scroll the content to the left.
屏幕剛顯示,未發生移動之前getScrollX()等於0,每一次scrollTo後會刷新getScrollX()的值。
在一次ACTION_MOVE事件中,手指移動了dx距離,那麼讓內容也一起移動dx,就實現了跟隨手指移動的效果。
相當於 getScrollX()+=dx。
當我們手指拿起來的時候,要判斷SwipeLayout的菜單欄是應該收縮還是應該展開。當確定了狀態後就要模擬手指觸摸滑動到指定位置。
/**
* move事件裡模擬滑動完成後,判斷展開狀態
* 再模擬滾動到目標位置
*/
public void SimulateScroll(int type){
int dx =0;
switch (type){
case EXPAND:
//手指向左滑動getScrollX為正
dx = rightViewWidth-getScrollX();
break;
case SHRINK:
//手指向右滑動getScrollX為負
dx = 0-getScrollX();
break;
default:
break;
}
scroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx)/2);
invalidate();
}
@Override
public void computeScroll() {
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
*
* 返回true代表正在模擬數據,false 已經停止模擬數據
*/
if (scroller.computeScrollOffset()) {
/**
* 更新X軸的偏移量
*/
scrollTo(scroller.getCurrX(), 0);
/**
* 遞歸調用computeScroll()方法,直到模擬滾動完成
*/
invalidate();
}
}
這裡調用invalidate()重繪UI,會再次調用computeScroll(),遞歸 直到 模擬滾動完成。
listview滑動時所有SwipeLayout復原。
我們知道listview刪除了一個item,不一定會回收view,有可能只是重新裝載了數據。那麼顯示的時候要SwipeLayout復原。
/**
* 用於上下滑動和刪除item時的,狀態改變
*/
static List swipelayouts = new ArrayList<>();
public static void addSwipeView(SwipeLayout v){
if(null==v){
return;
}
swipelayouts.add(v);
}
public static void removeSwipeView(SwipeLayout v){
if(null==v){
return;
}
v.SimulateScroll(SwipeLayout.SHRINK);
}
private void shrinkAllView(){
for(SwipeLayout s :swipelayouts){
if(null==s){
swipelayouts.remove(s);
continue;
}else {
s.SimulateScroll(SwipeLayout.SHRINK);
}
}
}
內部定義了三個方法,當刪除item時調用removeSwipeView方法使該view復原。在adapte的getview方法添加新item時調用addSwipeView,把當前顯示的所有SwipeLayout都裝在這個list裡面。在上面dispatchTouchEvent中判斷為上下滑動事件的時候調用shrinkAllView復原所有。
基本上就完了,有木有很簡單!哈哈
我們這裡是解決了上下滑動和左右滑動的沖突,那麼listview 中item為scrollview時,兩個都要豎著滑動。
我們為什麼想要在listview 中使用scrollview,因為我們的item中需要顯示的太多。我們知道當scrollview裡的內容高度 小於它父view給他的高度的時候,它是完全展開的,不需要也不能滑動。那麼使listview給item足夠大的高度,讓scrollview不必以滾動的方式來展現,就可以解決這個滑動沖突,反正滾動事件也會被listview 攔截。
新建一個新的NoScrollListView繼承與listview,並復寫onMeasure方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//這好比你有一個炒雞有錢的爹,你想要多少都能滿足你,對! 是滿足你...
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 7, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
那麼我有一個問題,viewpage+fragment+listview +SwipeLayout,我們知道viewpage要左右滑動,當發生滑動事件的時候,我是該移動SwipeLayout呢?還是讓viewpage子去翻頁呢?
序言最近在研究直播的彈幕,東西有點多,准備記錄一下免得自己忘了又要重新研究,也幫助有這方面需要的同學少走點彎路。關於直播的技術細節其實就是兩個方面一個是推流一個是拉流,而
菜單功能是點擊按鈕彈出分類菜單 看看效果圖 先說一下實現原理,彈出菜單采用的是Fragment實現,很方便且高效,上面的三個按鈕是RadioButton。 新建一個項目
很多Android手機隨機都預裝了很多無法卸載的第三方APP,這些APP既浪費資源還有偷跑流量的隱患。那麼,在不Root系統的前提下如何將它們“
如果你想在你的Android程序中自動打印MainActivity.onCreate(line:37)這種類名.方法名(行數)的日志該如何實現呢? 1.引入Java的線程