`

向上拖动时,可以惯性滑动显示到下一页的控件DragLayout

阅读更多
仿照淘宝和聚美优品,在商品详情页,向上拖动时,可以加载下一页。使用ViewDragHelper,滑动比较流畅。 scrollView滑动到底部的时候,再行向上拖动时,添加了一些阻力。

只支持两页!




import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

/**
 * 这是一个viewGroup容器,实现上下两个frameLayout拖动切换
 * 
 * @author sistone.Zhang
 */
@SuppressLint("NewApi")
public class DragLayout extends ViewGroup {

	/* 拖拽工具类 */
	private final ViewDragHelper mDragHelper;
	private GestureDetectorCompat gestureDetector;

	/* 上下两个frameLayout,在Activity中注入fragment */
	private View frameView1, frameView2;
	private int viewHeight;
	private static final int VEL_THRESHOLD = 100; // 滑动速度的阈值,超过这个绝对值认为是上下
	private static final int DISTANCE_THRESHOLD = 100; // 单位是像素,当上下滑动速度不够时,通过这个阈值来判定是应该粘到顶部还是底部
	private int downTop1; // 手指按下的时候,frameView1的getTop值
	private ShowNextPageNotifier nextPageListener; // 手指松开是否加载下一页的notifier

	public DragLayout(Context context) {
		this(context, null);
	}

	public DragLayout(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public DragLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mDragHelper = ViewDragHelper
				.create(this, 10f, new DragHelperCallback());
		mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_BOTTOM);
		gestureDetector = new GestureDetectorCompat(context,
				new YScrollDetector());
	}

	@Override
	protected void onFinishInflate() {
		// 跟findviewbyId一样,初始化上下两个view
		frameView1 = getChildAt(0);
		frameView2 = getChildAt(1);
	}

	class YScrollDetector extends SimpleOnGestureListener {

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx,
				float dy) {
			// 垂直滑动时dy>dx,才被认定是上下拖动
			return Math.abs(dy) > Math.abs(dx);
		}
	}

	@Override
	public void computeScroll() {
		if (mDragHelper.continueSettling(true)) {
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}

	/**
	 * 这是拖拽效果的主要逻辑
	 */
	private class DragHelperCallback extends ViewDragHelper.Callback {

		@Override
		public void onViewPositionChanged(View changedView, int left, int top,
				int dx, int dy) {
			int childIndex = 1;
			if (changedView == frameView2) {
				childIndex = 2;
			}

			// 一个view位置改变,另一个view的位置要跟进
			onViewPosChanged(childIndex, top);
		}

		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			// 两个子View都需要跟踪,返回true
			return true;
		}

		@Override
		public int getViewVerticalDragRange(View child) {
			// 这个用来控制拖拽过程中松手后,自动滑行的速度,暂时给一个随意的数值
			return 1;
		}

		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			// 滑动松开后,需要向上或者乡下粘到特定的位置
			animTopOrBottom(releasedChild, yvel);
		}

		@Override
		public int clampViewPositionVertical(View child, int top, int dy) {
			int finalTop = top;
			if (child == frameView1) {
				// 拖动的时第一个view
				if (top > 0) {
					// 不让第一个view往下拖,因为顶部会白板
					finalTop = 0;
				}
			} else if (child == frameView2) {
				// 拖动的时第二个view
				if (top < 0) {
					// 不让第二个view网上拖,因为底部会白板
					finalTop = 0;
				}
			}

			// finalTop代表的是理论上应该拖动到的位置。此处计算拖动的距离除以一个参数(3),是让滑动的速度变慢。数值越大,滑动的越慢
			return child.getTop() + (finalTop - child.getTop()) / 3;
		}
	}

	/**
	 * 滑动时view位置改变协调处理
	 * 
	 * @param viewIndex
	 *            滑动view的index(1或2)
	 * @param posTop
	 *            滑动View的top位置
	 */
	private void onViewPosChanged(int viewIndex, int posTop) {
		if (viewIndex == 1) {
			int offsetTopBottom = viewHeight + frameView1.getTop()
					- frameView2.getTop();
			frameView2.offsetTopAndBottom(offsetTopBottom);
		} else if (viewIndex == 2) {
			int offsetTopBottom = frameView2.getTop() - viewHeight
					- frameView1.getTop();
			frameView1.offsetTopAndBottom(offsetTopBottom);
		}

		// 有的时候会默认白板,这个很恶心。后面有时间再优化
		invalidate();
	}

	private void animTopOrBottom(View releasedChild, float yvel) {
		int finalTop = 0; // 默认是粘到最顶端
		if (releasedChild == frameView1) {
			// 拖动第一个view松手
			if (yvel < -VEL_THRESHOLD
					|| (downTop1 == 0 && frameView1.getTop() < -DISTANCE_THRESHOLD)) {
				// 向上的速度足够大,就滑动到顶端
				// 向上滑动的距离超过某个阈值,就滑动到顶端
				finalTop = -viewHeight;

				// 下一页可以初始化了
				if (null != nextPageListener) {
					nextPageListener.onDragNext();
				}
			}
		} else {
			// 拖动第二个view松手
			if (yvel > VEL_THRESHOLD
					|| (downTop1 == -viewHeight && releasedChild.getTop() > DISTANCE_THRESHOLD)) {
				// 保持原地不动
				finalTop = viewHeight;
			}
		}

		if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}

	/* touch事件的拦截与处理都交给mDraghelper来处理 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {

		if (frameView1.getBottom() > 0 && frameView1.getTop() < 0) {
			// view粘到顶部或底部,正在动画中的时候,不处理touch事件
			return false;
		}

		boolean yScroll = gestureDetector.onTouchEvent(ev);
		boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
		int action = ev.getActionMasked();

		if (action == MotionEvent.ACTION_DOWN) {
			// action_down时就让mDragHelper开始工作,否则有时候导致异常 他大爷的
			mDragHelper.processTouchEvent(ev);
			downTop1 = frameView1.getTop();
		}

		return shouldIntercept && yScroll;
	}

	@Override
	public boolean onTouchEvent(MotionEvent e) {
		// 统一交给mDragHelper处理,由DragHelperCallback实现拖动效果
		mDragHelper.processTouchEvent(e); // 该行代码可能会抛异常,正式发布时请将这行代码加上try catch
		return true;
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// 只在初始化的时候调用
		// 一些参数作为全局变量保存起来
		frameView1.layout(l, 0, r, b - t);
		frameView2.layout(l, 0, r, b - t);

		viewHeight = frameView1.getMeasuredHeight();
		frameView2.offsetTopAndBottom(viewHeight);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		measureChildren(widthMeasureSpec, heightMeasureSpec);

		int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
		int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
		setMeasuredDimension(
				resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
				resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
	}

	/**
	 * 这是View的方法,该方法不支持android低版本(2.2、2.3)的操作系统,所以手动复制过来以免强制退出
	 */
	public static int resolveSizeAndState(int size, int measureSpec,
			int childMeasuredState) {
		int result = size;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);
		switch (specMode) {
		case MeasureSpec.UNSPECIFIED:
			result = size;
			break;
		case MeasureSpec.AT_MOST:
			if (specSize < size) {
				result = specSize | MEASURED_STATE_TOO_SMALL;
			} else {
				result = size;
			}
			break;
		case MeasureSpec.EXACTLY:
			result = specSize;
			break;
		}
		return result | (childMeasuredState & MEASURED_STATE_MASK);
	}

	public void setNextPageListener(ShowNextPageNotifier nextPageListener) {
		this.nextPageListener = nextPageListener;
	}

	public interface ShowNextPageNotifier {
		public void onDragNext();
	}
}


用法:
private void initView() {
		fragment1 = new VerticalFragment1();
		fragment2 = new VerticalFragment2();
//		fragment3 = new VerticalFragment3();

		getSupportFragmentManager().beginTransaction()
				.add(R.id.first, fragment1)
				.add(R.id.second, fragment2)
//				.add(R.id.second, fragment3)//只支持两页
				.commit();

		ShowNextPageNotifier nextIntf = new ShowNextPageNotifier() {
			@Override
			public void onDragNext() {
//				fragment3.initView();
			}
		};
		draglayout = (DragLayout) findViewById(R.id.draglayout);
		draglayout.setNextPageListener(nextIntf);
	}


布局:
<com.stone.verticalslide.DragLayout
        android:id="@+id/draglayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <FrameLayout
            android:id="@+id/first"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />

        <FrameLayout
            android:id="@+id/second"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    </com.stone.verticalslide.DragLayout>


////////////////////////////////////////////////////////////////////
仿淘宝商品浏览界面, 向上拉查看详情
http://blog.csdn.net/mr_wanggang/article/details/46356421


这是一个多功能的扩展GridView 可展开,可拖动,可排序,可删除。 固定更多按钮。 展开合并支持动画。 支持箭头图标移动。 数据的处理和显示使用Bean。 来自于500彩票Andorid客户端首页功能。
http://www.jcodecraeer.com/a/opensource/2015/0827/3376.html

自定义商品详情页
http://blog.csdn.net/qq_22271479/article/details/68490868
  • 大小: 1.1 MB
分享到:
评论

相关推荐

    Android ScrollView取消惯性滚动的方法

    当调用fling()时,我们不是直接传递原始的velocity值,而是将其除以1000,这样可以显著减小滚动速度,使得滚动更接近于用户的拖拽操作,从而达到取消惯性滚动的效果。 此外,该示例还包含了一个ScrollView监听器...

    控件以及双指放大缩小图片、单指拖动图片.zip

    8. 惯性滚动:为了提供更流畅的用户体验,还可以实现惯性滚动,即在用户停止滑动后,图片能继续滑动一段距离,然后慢慢减速至停止。这需要对滑动速度进行估算和模拟。 9. 触摸事件的多线程处理:在处理大量图片时,...

    ViewPager滑动监听

    - `onPageScrollStateChanged(int state)`: 当滑动状态改变时调用,state有三种状态:SCROLL_STATE_IDLE(静止),SCROLL_STATE_DRAGGING(拖动)和SCROLL_STATE_SETTLING(惯性滑动)。 **3. 实现动态TAB下划线...

    android实现uc和墨迹天气那样的左右拖动效果

    当用户停止滑动时,`Scroller`可以继续执行剩余的滚动距离,模拟真实世界的惯性效果。在自定义`ViewGroup`中,可以通过`computeScroll()`方法来更新视图的位置。 5. **动画库(Animation Library)** - Android的...

    mobile实例の开发炫丽滑动效果

    - 惯性滑动:模拟真实世界的物理行为,让滑动在手指离开屏幕后继续一段时间。 - 反馈:提供视觉和听觉反馈,如滑动时改变颜色、播放声音等,以增强用户感知。 6. **测试与调试**: - 使用Visual Studio的模拟器...

    高级控件-翻页类视图

    状态包括:SCROLL_STATE_IDLE(静止)、SCROLL_STATE_DRAGGING(拖拽中)、SCROLL_STATE_SETTLING(滑动后惯性运动)。 - **onPageScrolled(int position, float positionOffset, int positionOffsetPixels)**: 在...

    模仿QQ微信列表左滑删除功能

    可以使用Android的动画库,例如ObjectAnimator,来平滑地改变Item的位置,模拟出惯性滑动的感觉。 5. **数据模型与适配器**:你需要一个数据模型来存储列表项的信息,并创建一个适配器(Adapter)来连接数据和视图...

    Android一个具有弹簧效果的RecyclerView

    SpringRecyclerView SpringRecyclerView是一个RecyclerView具有弹簧效果,当被拖动或flinged到overScroll。 ListView版本:https : //github.com/gjiazhe/SpringListView

    可以双击放大的gallery

    滑动(Swiping)是`RecyclerView`的默认行为,但在自定义实现中可能需要额外的优化,例如添加惯性滑动效果或处理边界条件。 项目中的`ImageTest`可能是测试用例或者一个示例图片列表的资源文件,它包含了用于展示...

    UIScrollView的横纵多页显示

    `UIScrollView`的内容区域可以比其实际显示区域大,这样就可以通过手势滑动查看超出部分的内容。为了实现两方向的滚动,我们需要设置`UIScrollView`的`contentSize`属性,使其在宽度和高度上都大于其自身尺寸。 1. ...

    转盘 改变颜色

    在惯性滚动结束后,可以使用一个更短的动画来精确地定位到最近的选项。 6. **事件处理**: 当转盘停止时,需要触发相应的事件,比如通知代理或更新关联的数据模型。这可以通过设置一个观察者或者在动画结束时调用...

    UIScrollView循环浏览图片

    当用户向左滑动时,我们需要更新图片的顺序,即将当前显示的图片(例如imageView1)移动到末尾,使其看起来像是下一张图片;同时,末尾的图片(例如imageView3)移动到最左侧,成为下一次滚动的新起始点。这个过程...

    Director行为解释[文].pdf

    6. Drag and Toss(拖动和抛出):用户可以移动角色,并在释放鼠标时产生惯性运动,实现抛物线运动效果。 7. Drag Quad Points(拖动顶角点):用户可拖动角色的顶角进行变形,适用于可变形对象的设计。 8. Drag ...

    Android 图片浏览功能简单实现(画廊效果实现,支持放大缩小)

    这可以通过自定义ViewPager的滑动速度和惯性实现。 6. **数据源**: 图片的来源可以是本地资源、网络URL,或者应用内的SQLite数据库。你需要根据实际情况选择合适的数据结构来存储图片信息,例如一个ArrayList存储...

    2014移动设计模式

    - **加载指示器**:在等待数据加载时显示,让用户知道正在发生的事情。 #### 四、输入捕捉 **1. 智能键盘** - 自动纠错功能:纠正拼写错误。 - 语境感知建议:根据上下文提供单词建议。 **2. 默认值与自动补全** -...

    WPF仿苹果界面

    1. **惯性滚动**:通过计算用户停止拖动时的速度,并根据这个速度来决定内容的滚动距离和时间,实现惯性效果。 2. **速度加速与减速**:模拟物理世界的加速度和减速规律,使得滚动在开始和结束时有明显的速度变化,...

    微信小程序-可移动菜单的实现过程详解

    默认值为`false`,设置为`true`则开启惯性滑动。 3. `out-of-bounds`: 如果设置为`true`,即使`movable-view`超出设定的可移动区域,仍然可以继续移动;默认值为`false`,超出范围后将自动回弹至可移动区域内。 4....

Global site tag (gtag.js) - Google Analytics