由于某种不能道明的原因,装了个天猫的apk,个人感觉主色调有点暗(当然程序员的审美不可信),看到首页上有个拖动广告的效果感觉还不错,就试着仿了一下(但是这个广告好像是暂时的我现在截不到原效果图了,sorry~~~),下面是实现的效果图:
接下来就看看我们要怎么实现这个效果吧—>>>
其实这个实现的原理也比较简单,我们也选择一种比较简单的实现方式,既然这里用到的是广告图片,那么最好就是继承系统原有控件ImageView,这样我们要做的工作就是专注于当手指操作这个图片时的动作了。在功能上面分析的话也就是当点击的时候执行点击事件,当手指在图片上移动的时候就记录下现在手指的位置,然后相应调整图片的位置,其实就是控件的位置,当手指松开的时候并且之前是在移动的话,那么就把图片移动到屏幕边上,不让他再中间挡住内容区域,好,知道我们要做什么了,接下来就该想想要怎么做了。
这里我的做法是,自定义DragImageView,继承自ImageView,然后重写ImageView的onTouchEvent函数(了解事件分发过程的朋友们应该会知道我们要想在这个控件内执行触摸事件的处理,就要在这个方法里写我们的逻辑),然后在这个函数里判断ACTIOIN_DOWNACTION_UP,ACTION_UP事件,并执行相应的操作,执行什么操作呢,上面已经说了大致的思路,下面我贴下代码,代码里会有详细的注释
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.widget.ImageView;
public class DragImageView extends ImageView{
private boolean isMove; //标志用户是否在移动此view
private int screenWidth,screenHeight; //屏幕的宽度
private float oldPosX,oldPosY; //记录上次点击的点的横纵坐标
private OnClickListener clickListener; //自定义的点击监听器,可以认为和view的onclicklistener一样
public DragImageView(Context context){
this(context, null);
}
public DragImageView(Context context, AttributeSet attrs) {
super(context, attrs);
DisplayMetrics dm = context.getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// setMeasuredDimension(600, 800);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float posX = event.getX(); //点击的x坐标,注意这个坐标是相对于此ImageView本身而言的相对坐标
float posY = event.getY(); //大概同上
switch(event.getAction()){
case MotionEvent.ACTION_DOWN: //执行按下操作时记录此时的点击位置,标记isMove为false
oldPosX = posX;
oldPosY = posY;
isMove = false;
return true;
case MotionEvent.ACTION_MOVE: //手指在此控件上移动时,设置isMove=true,并按计算的距离移动此控件
isMove = true;
offsetLeftAndRight((int) (posX-oldPosX));
offsetTopAndBottom((int) (posY-oldPosY));
return true;
case MotionEvent.ACTION_UP:
if(isMove){ //如果之前在移动的话,判断是否在屏幕中间
if(event.getRawX()+getWidth()/2-oldPosX<screenWidth/2){ //在稍微靠左的地方
//执行向左的动画
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "x", 0.0f);
animator.setDuration(1000);
animator.start();
}else{ //在稍微靠右的地方
//执行向右的动画
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "x", screenWidth-getWidth());
animator.setDuration(1000);
animator.start();
}
//避免让控件超出屏幕
if(getY()<0){
setY(0.0f);
}else if(getY()>screenHeight-getHeight()){
setY(screenHeight-getHeight());
}
}else{ //如果之前是在点击的话,执行点击事件
if(this.clickListener!=null){
this.clickListener.onClick(this);
}
}
break;
}
return true; //表示action_down和action_move后下一个事件可以传过来,actionup后事件不再向下传递
}
@Override
public void setOnClickListener(OnClickListener onClickListener){
this.clickListener = onClickListener;
}
@Override
public boolean performClick() {
return super.performClick();
}
}
逻辑并不复杂吧,但是要真正实现起来还是需要一些其他的理论知识的,比如之所以我们要在onTouchEvent里写就是考虑到了事件的分发过程中只要此控件的父控件或者祖宗控件没有拦截事件,就会执行到此控件的这个函数,而之所以我们在每个动作后都返回了true也是有不同含义的,比如ACTION_DOWN和ACTION_MOVE动作后返回true是想让事件继续传递,否则是接受不到下面的ACTION_UP的事件的,这方面就不多说了,总结一句话就是:一个事件总是从ACTION_DOWN开始,以ACTION_UP结束的,在ACTION_UP后返回true是想让这个控件彻底消费掉事件不继续再往下传递了。
另外还有一点要注意的是MotionEvent的几个函数,以getX和getRawX为例,getX得到的是点击的坐标相对于此控件的位置,而getRawX是相对于整个屏幕的位置,也就是绝对位置,这个应该比较好理解吧,只是大家不知道他们俩的区别的话会很容易走到死胡同里怎么也出不来(ps:这也是我当年的痛啊)
还有比较重要的一点,也是实现控件跟你手指移动的比较关键的一点,是在这里:
case MotionEvent.ACTION_MOVE:
//手指在此控件上移动时,设置isMove=true,并按计算的距离移动此控件
isMove = true;
offsetLeftAndRight((int) (posX-oldPosX));
offsetTopAndBottom((int) (posY-oldPosY));
return true;
可以看到我们在ACTION_DOWN的时候,记录下了现在手指点击的坐标,然后在每次ACTION_MOVE的时候算一下现在控件应该偏移多少,很简单就是现在的坐标-原来的坐标就好了,而offsetLeftAndRight这个函数就是根据传递的水平方向的移动距离参数来移动控件位置并刷新,offsetTopAndBottom也是类似,这一点还是很关键的
然后就是ACTION_UP事件
case MotionEvent.ACTION_UP:
if(isMove){ //如果之前在移动的话,判断是否在屏幕中间
if(event.getRawX()+getWidth()/2-oldPosX<screenWidth/2){ //在稍微靠左的地方
//执行向左的动画
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "x", 0.0f);
animator.setDuration(1000);
animator.start();
}else{ //在稍微靠右的地方
//执行向右的动画
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "x", screenWidth-getWidth());
animator.setDuration(1000);
animator.start();
}
//避免让控件超出屏幕
if(getY()<0){
setY(0.0f);
}else if(getY()>screenHeight-getHeight()){
setY(screenHeight-getHeight());
}
}else{ //如果之前是在点击的话,执行点击事件
if(this.clickListener!=null){
this.clickListener.onClick(this);
}
}
break;
因为我们不想让控件超出屏幕而且也不希望控件在屏幕中间挡住其他部分的内容,所以我们在手指抬起的时候首先判断一下之前的一系列动作是点击还是移动,也就是isMove这个变量,如果是点击的话就比较简单了,执行onClick,当然这里我们是有点偷梁换柱了,细节大家去看吧,这并不是重点;如果是移动的话判断现在控件的位置,是离左边近还是右边近,然后执行一个小动画,这里我设置的动画是1s,其实这里也是可以用要移动的距离/你希望的移动速度来计算一下,这样可能更好一些
ok,大概就是这样,再贴一下设置点击监听器的代码:
mDragImageView = (DragImageView) findViewById(R.id.img);
mDragImageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "点我干嘛", Toast.LENGTH_SHORT).show();
}
});
嗯,那就是这样了,弄了一晚上弄出来的,和大家分享一下,希望能帮助有需要的朋友,当然也欢迎大家提点建议,支持请给个赞呦
另外大家有想要代码的话麻烦留个言,要不我也懒得传上去了