java中setDragMode方法_Android 常见 View 控件 滑动 和 拖动的几种方式

本文详细介绍了在Android中实现View滑动和拖动的多种方式,包括使用scrollTo/scrollBy方法、动画、LayoutParams、Scroller以及ViewDragHelper。针对每种方法,不仅阐述了其工作原理,还提供了具体的代码示例,帮助开发者理解和实现View的滑动与拖动效果。
摘要由CSDN通过智能技术生成

(1)使用scrollTo/scrollBy实现View的滑动

实现滑动的最朴素直接的方式就是使用View类自带的scrollTo/scrollBy方法了。

可以直接linearLayout.getScaleY(); 和 查看 mScrollX 变量,源码里面看注释就可以发现:

mScrollX:表示离视图起始位置的x水平方向的偏移量

mScrollY:表示离视图起始位置的y垂直方向的偏移量

分别通过getScrollX() 和getScrollY()方法获得。

注意:mScrollX和mScrollY指的并不是坐标,而是偏移量。

public void scrollTo(int x, int y) {

if (mScrollX != x || mScrollY != y) {

int oldX = mScrollX;

int oldY = mScrollY;

mScrollX = x;

mScrollY = y;

onScrollChanged(mScrollX, mScrollY, oldX, oldY);

if (!awakenScrollBars()) {

invalidate();

}

}

}

public void scrollBy(int x, int y) {

scrollTo(mScrollX + x, mScrollY + y);

}

从以上的代码可以看出,scrollTo 和 scrollBy区别,其实2者的效果是一样的。

scrollTo(int x,int y):

如果偏移位置发生了改变,就会给mScrollX和mScrollY赋新值,改变当前位置。

注意:x,y代表的不是坐标点,而是偏移量。

例如:

我要移动view到坐标点(100,100),那么我的偏移量就是(0,,0) -(100,100) = (-100,-100),我就要执行view.scrollTo(-100,-100),达到这个效果。

scrollBy(int x,int y):

从源码中看出,它实际上是调用了scrollTo(mScrollX + x, mScrollY + y);mScrollX + x和mScrollY + y,即表示在原先偏移的基础上在发生偏移,通俗的说就是相对我们当前位置偏移。

scrollTo方法是滑动到指定位置,而scrollBy方法是滑动指定的位移量,在原先的基础上发生偏移。

根据父类VIEW里面移动,如果移动到了超出的地方,就不会显示。

db1a6e19b7837f27177e203af5316fed.gif

代码如下 :

29ffc99c3a260cf7f6b5f8bfcf9677d2.png

我们了解到使用scrollTo/scrollBy方法实现View的滑动是很简单直接的,那么简单的背后有什么代价呢?代价就是滑动不是“弹性的”,可以用个定时器不停的调用,让它开起来是顺滑的。

(2)使用 动画 实现View的滑动

使用动画来实现View的滑动主要通过改变View的translationX和translationY参数来实现,使用动画的好处在于滑动效果是平滑的。

上面我们提到过,View的x、y参数决定View的当前位置,通过改变translationX和translationY,我们就可以改变View的当前位置。

我们可以使用属性动画或者补间动画来实现View的平移。

使用动画的实现方式比较简单,下面就使用间补动画和属性动画做个案例,如下 :

首先,我们先来看一下如何使用补间动画来实现View的平移。补间动画资源定义如下(anim.xml):

android:fillAfter="true">

android:duration="100"

android:fromXDelta="0"

android:fromYDelta="0"

android:interpolator="@android:anim/linear_interpolator"

android:toXDelta="100"

android:toYDelta="100"/>

然后在onCreat方法中调用startAnimation方法即可。使用补间动画实现View的滑动有一个缺陷,那就是移动的知识View的“影像”,这意味着其实View并未真正的移动,只是我们看起来它移动了而已。拿Button来举例,假若我们通过补间动画移动了一个Button,我们会发现,在Button的原来位置点击屏幕会出发点击事件,而在移动后的Button上点击不会触发点击事件。

接下来,我们看看如何用属性动画来实现View的平移。使用属性动画实现View的平移更加简单,只需要以下一条语句:

button.setOnClickListener((v) -> {

float translationX = (int) v.getTranslationX();

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(v, "translationX", translationX, -100, translationX);

objectAnimator.setDuration(3000);

objectAnimator.start();

});

以上代码即实现了使用属性动画把targetView在3秒内向右平移100px。使用属性动画的限制在于真正的属性动画只可以在Android 3.0+使用(一些第三方库实现的兼容低版本的属性动画不是真正的属性动画),优点就是它可以真正的移动View而不是仅仅移动View的影像。

经过以上的描述,使用属性动画实现View的滑动看起来是个不错的选择,而且一些View的复杂的滑动效果只有通过动画才能比较方便的实现。

属性动画实现效果如下 : (其实很顺滑的,只是录制效果有点差。)

0e760c71d8a8a0e4a4ae7f375472ddc2.gif

(3)使用 LayoutParams 实现View的滑动

通过改变布局参数来实现View的滑动的思想很简单:

比如向右移动一个View,只需要把它的marginLeft参数增大,向其它方向移动同理,只需改变相应的margin参数。

还有一种比较拐弯抹角的方法是在要移动的View的旁边预先放一个View(初始宽高设为0)。

然后比如我们要向右移动View,只需把预先放置的那个View的宽度增大,这样就把View“挤”到右边了。代码示例如下:

LinearLayout.LayoutParams lps = (LinearLayout.LayoutParams) v.getLayoutParams();

lps.leftMargin += 100;

v.requestLayout();

以上代码即实现了把mButton向右滑动100px。通过改变布局参数来实现的滑动效果也不是平滑的。

(4)使用Scroller来实现弹性滑动

Android里Scroller类是为了实现View平滑滚动的一个Helper类。通常在自定义的View时使用,在View中定义一个私有成员mScroller = new Scroller(context)。设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动。 相关API介绍如下

mScroller.getCurrX() //获取mScroller当前水平滚动的位置

mScroller.getCurrY() //获取mScroller当前竖直滚动的位置

mScroller.getFinalX() //获取mScroller最终停止的水平位置

mScroller.getFinalY() //获取mScroller最终停止的竖直位置

mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置

mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置

//滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间

mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms

mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)

mScroller.computeScrollOffset() //返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束。

举例说明,自定义一个CustomView,使用Scroller实现滚动:

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.View;

import android.widget.LinearLayout;

import android.widget.Scroller;

public class CustomView extends LinearLayout {

private static final String TAG = "Scroller";

private Scroller mScroller;

public CustomView(Context context, AttributeSet attrs) {

super(context, attrs);

mScroller = new Scroller(context);

}

//调用此方法滚动到目标位置

public void smoothScrollTo(int fx, int fy) {

int dx = fx - mScroller.getFinalX();

int dy = fy - mScroller.getFinalY();

smoothScrollBy(dx, dy);

}

//调用此方法设置滚动的相对偏移

public void smoothScrollBy(int dx, int dy) {

//设置mScroller的滚动偏移量

mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);

invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果

}

@Override

public void computeScroll() {

//先判断mScroller滚动是否完成

if (mScroller.computeScrollOffset()) {

//这里调用View的scrollTo()完成实际的滚动

scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

//必须调用该方法,否则不一定能看到滚动效果

postInvalidate();

}

super.computeScroll();

}

}

效果图如下 :

e30247f1d21a669fcd19f4e4734d31c9.gif

(5)使用ViewDragHelper来实现View拖动

ViewDragHelper从名称上就可以看出, 这是一个用来简化view拖拽操作的帮助类。而且使用起来也很简单, 很方便,只需要几个方法和1个Callback就可以实现一个可以拖动到view。

需要注意个是:ViewDragHelper是作用在一个ViewGroup上,也就是说他不能直接作用到被拖拽的view, 其实这也很好理解,因为view在布局中的位置是父ViewGroup决定的。

如何使用ViewGroup实现一个可以拖动的view?

1、获取ViewDragHelper的实例,注意,这里不能直接new,而是使用ViewDragHelper的一个静态方法:

ViewDragHelper.create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb);

参数1: 一个ViewGroup, 也就是ViewDragHelper将要用来拖拽谁下面的子view

参数2:灵敏度,一般设置为1.0f就行

参数3:一个回调,用来处理拖动到位置

2、继承ViewDragHelper.Callback类,该类有个抽象方法:tryCaptureView(View view, int pointerId) 表示尝试捕获子view,这里一定要返回true, 返回true表示允许。

3、重写两个方法int clampViewPositionHorizontal(View child, int left, int dx)和int clampViewPositionHorizontal(View child, int left, int dx) 这两个方法分别用来处理x方向和y方向的拖动的,返回值该child现在的位置。

4、重写ViewGroup的onInterceptTouchEvent(MotionEvent ev)用来拦截事件

5、重写ViewGroup的onTouchEvent(MotionEvent event) 在这里面只要做两件事:mDragHelper.processTouchEvent(event);处理拦截到的事件,这个方法会在返回前分发事件;return true 表示消费了事件。

简单的5步就可以实现一个可以任意拖动到view了,当然我们需要在clampViewPositionHorizontal和clampViewPositionHorizontal中做一点工作,以防止view超出了边界。

下面我们自己定义一个CustomViews来实现可以随意拖动的View

public class CustomViews extends LinearLayout {

private ViewDragHelper mDragHelper;

public CustomViews(Context context) {

super(context);

init();

}

public CustomViews(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

public CustomViews(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

init();

}

private void init() {

/**

* @params ViewGroup forParent 必须是一个ViewGroup

* @params float sensitivity 灵敏度

* @params Callback cb 回调

*/

mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragCallback());

}

private class ViewDragCallback extends ViewDragHelper.Callback {

/**

* 尝试捕获子view,一定要返回true

*

* @param view      尝试捕获的view

* @param pointerId 指示器id?

*                  这里可以决定哪个子view可以拖动

*/

@Override

public boolean tryCaptureView(View view, int pointerId) {

return true;

}

/**

* 处理水平方向上的拖动

*

* @param child 被拖动到view

* @param left  移动到达的x轴的距离

* @param dx    建议的移动的x距离

*/

@Override

public int clampViewPositionHorizontal(View child, int left, int dx) {

System.out.println("left = " + left + ", dx = " + dx);

// 两个if主要是为了让viewViewGroup里

if (getPaddingLeft() > left) {

return getPaddingLeft();

}

if (getWidth() - child.getWidth() < left) {

return getWidth() - child.getWidth();

}

return left;

}

/**

* 处理竖直方向上的拖动

*

* @param child 被拖动到view

* @param top   移动到达的y轴的距离

* @param dy    建议的移动的y距离

*/

@Override

public int clampViewPositionVertical(View child, int top, int dy) {

// 两个if主要是为了让viewViewGroup里

if (getPaddingTop() > top) {

return getPaddingTop();

}

if (getHeight() - child.getHeight() < top) {

return getHeight() - child.getHeight();

}

return top;

}

/**

* 当拖拽到状态改变时回调

*

* @params 新的状态

*/

@Override

public void onViewDragStateChanged(int state) {

switch (state) {

case ViewDragHelper.STATE_DRAGGING:  // 正在被拖动

break;

case ViewDragHelper.STATE_IDLE:  // view没有被拖拽或者 正在进行fling/snap

break;

case ViewDragHelper.STATE_SETTLING: // fling完毕后被放置到一个位置

break;

}

super.onViewDragStateChanged(state);

}

}

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_DOWN:

mDragHelper.cancel(); // 相当于调用 processTouchEvent收到ACTION_CANCEL

break;

}

/**

* 检查是否可以拦截touch事件

* 如果onInterceptTouchEvent可以return true 则这里return true

*/

return mDragHelper.shouldInterceptTouchEvent(ev);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

/**

* 处理拦截到的事件

* 这个方法会在返回前分发事件

*/

mDragHelper.processTouchEvent(event);

return true;

}

}

XML 使用如下 :

android:id="@+id/customViews"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center"

android:orientation="vertical">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="移动我" />

效果图 如下 :

08d659d9a0a32e451f0e411b1537f814.gif

(6)offsetLeftAndRight() offsetTopAndBottom()

其实这两个方法分别是对左右移动和上下移动的封装,传入的就是偏移量。

下面就写一个案例,Java代码如下 :

public class MyText extends TextView {

int lastX;

int lastY;

public MyText(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

//获取到手指处的横坐标和纵坐标

int x = (int) event.getX();

int y = (int) event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

lastX = x;

lastY = y;

break;

case MotionEvent.ACTION_MOVE:

//计算移动的距离

int offX = x - lastX;

int offY = y - lastY;

offsetLeftAndRight(offX);

offsetTopAndBottom(offY);

break;

}

return true;//记得返回true,说明被我们这里消化了改事件

}

}

XML 使用方法如下 :

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="移动我" />

效果图如下 :

61a392c97f1a581eed8509330e431df5.gif

(7) layout()

如果你将滑动后的目标位置的坐标传递给layout(),这样子就会把view的位置给重新布置了一下,在视觉上就是view的一个滑动的效果。

这个方法和 第6个是一样的 只不过最后调用的方法不一样罢了

public boolean onTouchEvent(MotionEvent event) {

//获取到手指处的横坐标和纵坐标

int x = (int) event.getX();

int y = (int) event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

lastX = x;

lastY = y;

break;

case MotionEvent.ACTION_MOVE:

//计算移动的距离

int offX = x - lastX;

int offY = y - lastY;

//调用layout方法来重新放置它的位置  ,其他的和第6个方法是一样的,只是下面调用的这行代码不一样的罢了。

layout(getLeft() + offX, getTop() + offY,

getRight() + offX, getBottom() + offY);

break;

}

return true;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以尝试使用自定义 View 实现 Android 左右拖动解锁动画。具体步骤如下: 1. 创建一个继承自 View 的类,重写 onDraw() 方法。 2. 在 onDraw() 方法绘制解锁控件的背景和滑块。 3. 在 onTouchEvent() 方法处理触摸事件,判断用户是否按下、移动或抬起了手指,并根据用户的操作更新滑块的位置。 4. 在更新滑块位置的同时,根据滑块的位置来判断用户是否已经完成了滑动操作。 5. 如果用户完成了滑动操作,可以触发一个回调方法来通知外部代码。 下面是一个简单的示例代码: ```java public class SlideUnlockView extends View { private Paint mPaint; private int mWidth; private int mHeight; private int mSliderWidth; private int mSliderHeight; private int mSliderLeft; private int mSliderTop; private int mSliderRight; private int mSliderBottom; private int mMaxLeft; private OnUnlockListener mListener; public SlideUnlockView(Context context) { super(context); init(); } public SlideUnlockView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SlideUnlockView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setAntiAlias(true); mSliderWidth = 200; mSliderHeight = 100; mMaxLeft = 400; mSliderLeft = 0; mSliderTop = 0; mSliderRight = mSliderLeft + mSliderWidth; mSliderBottom = mSliderTop + mSliderHeight; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.WHITE); canvas.drawRect(0, 0, mWidth, mHeight, mPaint); canvas.drawRect(mSliderLeft, mSliderTop, mSliderRight, mSliderBottom, mPaint); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = MeasureSpec.getSize(widthMeasureSpec); mHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(mWidth, mHeight); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (event.getX() >= mSliderLeft && event.getX() <= mSliderRight && event.getY() >= mSliderTop && event.getY() <= mSliderBottom) { return true; } break; case MotionEvent.ACTION_MOVE: int x = (int) event.getX(); if (x < 0) { x = 0; } if (x > mMaxLeft) { x = mMaxLeft; } mSliderLeft = x; mSliderRight = mSliderLeft + mSliderWidth; invalidate(); break; case MotionEvent.ACTION_UP: if (mSliderLeft == mMaxLeft && mListener != null) { mListener.onUnlock(); } mSliderLeft = 0; mSliderRight = mSliderLeft + mSliderWidth; invalidate(); break; } return super.onTouchEvent(event); } public void setOnUnlockListener(OnUnlockListener listener) { mListener = listener; } public interface OnUnlockListener { void onUnlock(); } } ``` 在这个示例代码,我们创建了一个名为 SlideUnlockView 的自定义 View,该控件支持左右拖动解锁功能。在 onTouchEvent() 方法,我们处理了触摸事件,并根据用户的操作更新了滑块的位置。在完成解锁操作时,我们触发了一个 OnUnlockListener 接口回调,以通知外部代码。 使用自定义 View 实现 Android 左右拖动解锁动画可能需要一些基本的绘图和触摸事件处理知识,但是它可以帮助开发人员创建出更加自由灵活的控件,并且可以满足更多的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值