先上一张效果图
中间的横向拖动就是我们要做的效果。
一、实现思路
仔细观察不难发现,该拖动view与listview的下拉刷新的效果很类似,手指拖动的时候显示隐藏的view,手指放开自动回弹。只不过区别就是一个横向一个纵向
下拉刷新的实现思路如下:
自定义一个布局继承自LinearLayout,然后在这个布局中加入下拉头和ListView这两个子元素,并让这两个子元素纵向排列。初始化的时候,让下拉头向上偏移出屏幕,这样我们看到的就只有ListView了。然后对ListView的touch事件进行监听,如果当前ListView已经滚动到顶部并且手指还在向下拉的话,那就将下拉头显示出来,松手后进行刷新操作,并将下拉头隐藏。原理如图所示:
我们要实现的效果跟下拉刷新基本一样,只不过是方向是横向而已。如图
二、具体实现
首先我们要先自定义一个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><!--此处添加 剩余显示的部分-->
</LinearLayout> </com.boohee.myview.HorizontalDragLinearLayout>
自定义的LinearLayout,初始化部分:
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);
在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 连接在
这里