Android 橫向帶有吸附效果的橫向拖動控制元件(效果同縱向下拉重新整理ListView)
阿新 • • 發佈:2019-01-04
先上一張效果圖
中間的橫向拖動就是我們要做的效果。
一、實現思路
仔細觀察不難發現,該拖動view與listview的下拉重新整理的效果很類似,手指拖動的時候顯示隱藏的view,手指放開自動回彈。只不過區別就是一個橫向一個縱向 下拉重新整理的實現思路如下: 自定義一個佈局繼承自LinearLayout,然後在這個佈局中加入下拉頭和ListView這兩個子元素,並讓這兩個子元素縱向排列。初始化的時候,讓下拉頭向上偏移出螢幕,這樣我們看到的就只有ListView了。然後對ListView的touch事件進行監聽,如果當前ListView已經滾動到頂部並且手指還在向下拉的話,那就將下拉頭顯示出來,鬆手後進行重新整理操作,並將下拉頭隱藏。原理如圖所示:我們要實現的效果跟下拉重新整理基本一樣,只不過是方向是橫向而已。如圖
二、具體實現
首先我們要先自定義一個LinearLayout,然後在佈局中加入隱藏頭和剩餘部分 佈局檔案如下:</LinearLayout> </com.boohee.myview.HorizontalDragLinearLayout> 自定義的LinearLayout,初始化部分:<com.boohee.myview.HorizontalDragLinearLayout android:id="@+id/main_hori_scroll_ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" android:baselineAligned="false" android:clickable="true" android:descendantFocusability="blocksDescendants" android:orientation="horizontal"> <!--新增一層處理左右拖動時的移動--> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" > ...........<pre name="code" class="java"><span style="white-space:pre"> </span><!--此處新增 剩餘顯示的部分-->
private Context context; //需要隱藏的view private TextView headerView; /** * 是否已載入過一次layout,這裡onLayout中的初始化只需載入一次 */ private boolean loadOnce; /** * 需要隱藏View的佈局引數 */ private MarginLayoutParams headerLayoutParams; /** * 隱藏view的寬度 */ private int hideHeaderWidth; private float xDown; /** * 每次move的x座標 */ private float tmpXMove; private float mFocusX = 0.f; /** * 在被判定為滾動之前使用者手指可以移動的最大值。 */ private int touchSlop; /** * 頭部回滾的速度 */ public static final int SCROLL_SPEED = -10; public HorizontalDragLinearLayout(Context context) { this(context, null); } public HorizontalDragLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HorizontalDragLinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setOrientation(HORIZONTAL); this.context = context; init(); } private void init() { touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); headerView = new TextView(context); LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_VERTICAL; headerView.setLayoutParams(params); headerView.setTextColor(Color.BLACK); headerView.setGravity(Gravity.CENTER); this.addView(headerView, 0); } public void setHeaderText(String string){ if (headerView != null){ headerView.setText(string); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (headerView != null && changed && !loadOnce){ hideHeaderWidth = - headerView.getWidth(); headerLayoutParams = (MarginLayoutParams) headerView.getLayoutParams(); headerLayoutParams.leftMargin = hideHeaderWidth; headerView.setLayoutParams(headerLayoutParams); loadOnce = true; } }
初始化的時候動態添加了一個TextView,也就是我們需要隱藏的部分 在第一次onLayout佈局的時候,我們根據隱藏頭的寬度 調整該隱藏頭的margin,使其隱藏。 然後我們需要進行拖動識別,重寫onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
//禁止父容器攔截事件
getParent().requestDisallowInterceptTouchEvent(true);
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
xDown = motionEvent.getRawX();
return true;
case MotionEvent.ACTION_MOVE:
float xMove = motionEvent.getRawX();
int distance = (int) (xMove - xDown);
if (distance <= 0 && headerLayoutParams.leftMargin <= hideHeaderWidth) {
return false;
}
if (distance < touchSlop) {
return false;
}
//大於頭部寬度不做處理
if (distance >= -hideHeaderWidth){
return true;
}
headerLayoutParams.leftMargin = distance + hideHeaderWidth;
headerView.setLayoutParams(headerLayoutParams);
tmpXMove = xMove;
return true;
case MotionEvent.ACTION_UP:
//鬆手時呼叫隱藏頭部
new HideHeaderTask().execute();
break;
default:
break;
}
return true;
}
由於外層是用的viewPager+fragment進行頁面切換,所以預設情況下touch事件是被viewpager消費掉的。我們要組織父控制元件攔截事件 getParent().requestDisallowInterceptTouchEvent(true);
其中涉及Android 的touch 分發,可以參考鴻洋大大的這篇部落格點選開啟連結 在move的時候得到移動的距離,然後對隱藏頭設定margin,使其及該LinearLayout的其他子view跟隨隱藏頭一起向右移動,達到我們想要的效果 up的時候啟動一個AsyncTask 動態改變隱藏頭的margin,去恢復到最初始的狀態,即隱藏頭隱藏的狀態。
/**
* 隱藏頭部
*/
class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {
@Override
protected Integer doInBackground(Void... params) {
int leftMargin = headerLayoutParams.leftMargin;
while (true) {
leftMargin = leftMargin + SCROLL_SPEED;
if (leftMargin <= hideHeaderWidth) {
leftMargin = hideHeaderWidth;
break;
}
publishProgress(leftMargin);
sleep(10);
}
return leftMargin;
}
@Override
protected void onProgressUpdate(Integer... leftMargin) {
headerLayoutParams.leftMargin = leftMargin[0];
headerView.setLayoutParams(headerLayoutParams);
}
@Override
protected void onPostExecute(Integer leftMargin) {
headerLayoutParams.leftMargin = leftMargin;
headerView.setLayoutParams(headerLayoutParams);
}
}
private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
搞定。 另外 效果中帶懸浮進度的progressBar 連線在這裡