您的位置:首页 > 移动开发 > Android开发

Android view的随手指拖动 仿IOS的悬浮圆圈效果

2016-08-27 16:19 363 查看
项目里面,有时会碰到需求,要求一个小图标或者布局悬浮于窗体上,像IOS中的小圆点一样

,最好有拖动功能。如何实现呢,分两步走,如果只是要在某个activity中能拖动,直接写

个拖动的自定义控件就行;如果是始终悬浮在应用程序的上面,这时候就要直接在Window中

添加了。

对于一个view,如果想拖动,可以处理MotionEvent事件,根据触摸事件,来计算位移和偏移

,重新绘制控件的位置。手指按下时,根据MotionEvent获取手指在屏幕上的距离,

event.getRawX();是手指按下的焦点到屏幕左上角的x轴的距离,event.getRawY();是y轴的

距离。补充一句event.getX()是焦点到控件本身左上角的x轴距离,同理event.getY()的功能

自己脑补。

根据MotionEvent判断事件的状态,分为按下,移动,起来。分别在不同的状态做不同的处理

,在MotionEvent.ACTION_DOWN时用两个成员变量 int值记录下按下的位置,startX = (int) 

event.getRawX();在MotionEvent.ACTION_MOVE时,获取新的焦点的值,int newX = (int) 

event.getRawX();计算偏移的量是多少,int dx = newX - startX;同时求出控件在父控件的

范围内的距离左边的距离,int left = iv_drag_view.getLeft();计算出新的距离左边的距

离,int nleft = left + dx;同理,可以得到控件右侧距离左边的距离,控件距离顶部的距

离等等四个参数。View有个函数 public void layout(int l, int t, int r, int b) {}可

以确定控件的位置,此时调用这个函数正好,控件的位置就移动了,此时再重新初始化手指

的开始位置startX = (int) event.getRawX(); 大体上移动的功能就做好了,如果要记录位

移上次的最终终点,可以在MotionEvent.ACTION_UP中记录一下控件距离左边和顶部的距离,

在下次重新进入该页面时,提前通过LayoutParams设置topMargin和leftMargin。

位移可以在view.setOnTouchListener(event)触摸监听里执行上述逻辑,也可以在view的

onTouchEvent(event)触摸事件机制里执行,看个人爱好。

上述逻辑还有一点小瑕疵,当我们拖着一个view到边界时,当控件的右侧触到屏幕的右边缘

时,此时应该做出处理,不能再往右拖动了,否则控件就会部分移出屏幕外边,UI体验不好

,此时应该在ACTION_MOVE中的view.layout(nleft, ntop, nright, nbottom);之前,把移动

事件跳出即可。if (nleft < 0 || ntop < 0
|| nright > 

wm.getDefaultDisplay().getWidth()
|| nbottom > 

wm.getDefaultDisplay().getHeight()) {
break;
}

给份相对完整的代码

class DragViewActivity extends Activity {
protected static final String TAG = "DragViewActivity";
private ImageView iv_drag_view;
private TextView tv_drag_view;
private WindowManager wm;

private SharedPreferences sp;

private long firstClickTime;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sp = getSharedPreferences("config", MODE_PRIVATE);
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
setContentView(R.layout.activity_drag_view);
iv_drag_view = (ImageView) findViewById(R.id.iv_drag_view);

int lastx = sp.getInt("lastx", 0);
int lasty = sp.getInt("lasty", 0);

// 通过布局参数设置 iv对象的位置, 在第一个阶段测量阶段生效
RelativeLayout.LayoutParams params = (LayoutParams) iv_drag_view
.getLayoutParams();
params.topMargin = lasty;
params.leftMargin = lastx;
iv_drag_view.setLayoutParams(params);

// 给imageview注册一个手指触摸的事件
iv_drag_view.setOnTouchListener(new OnTouchListener() {
int startX;
int startY;

@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:// 手指触摸到屏幕的

时候对应的事件
Log.i(TAG, "摸到!!");
startX = (int) event.getRawX();
startY = (int) event.getRawY();

break;

case MotionEvent.ACTION_MOVE:// 手指在屏幕上移动

的时候对应的事件
Log.i(TAG, "移动!!");
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
// 计算偏移量
int dx = newX - startX;
int dy = newY - startY;

int left = iv_drag_view.getLeft();
int right = iv_drag_view.getRight();
int top = iv_drag_view.getTop();
int bottom = iv_drag_view.getBottom();
// 计算出来新的坐标
int nleft = left + dx;
int nright = right + dx;
int ntop = top + dy;
int nbottom = bottom + dy;

if (nleft < 0 || ntop < 0
|| nright > 

wm.getDefaultDisplay().getWidth()
|| nbottom > 

wm.getDefaultDisplay().getHeight()) {
break;
}

// 更新imageview在屏幕上的位置.
iv_drag_view.layout(nleft, ntop, nright, 

nbottom);

// 重新初始化手指的开始位置
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;

case MotionEvent.ACTION_UP:// 手指离开屏幕时候对

应的事件
Log.i(TAG, "放手!!");
Editor editor = sp.edit();
editor.putInt("lastx", 

iv_drag_view.getLeft());
editor.putInt("lasty", 

iv_drag_view.getTop());
editor.commit();
break;
}

return true;// 当前监听器 会消费掉这次事件
}
});

}

}

layout布局

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

    <ImageView

        android:id="@+id/iv_drag_view"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginTop="80dip"

        android:src="@drawable/drag" />

</RelativeLayout>

RelativeLayout.LayoutParams params = (LayoutParams) iv_drag_view
.getLayoutParams();

这一部由布局控制,iv_drag_view的父布局是什么类型的布局,这里对应的就是那个

LayoutParams。有兴趣的可以google一下。

至于长时间显示在各个界面之上的悬浮框,就需要借助service和windowManger了。

在service里面,启动时创建一个WindowManager和一个布局View,然后通过

windowManager.addView(view, params);添加该布局到窗口中,params里面则设置一些该布

局的一些属性,比如宽 高 显示层级等等,在一个类里面转换布局,肯定用到

LayoutInflater,获取LayoutInflater的四种写法,最终都是一样的,调用

LayoutInflater LayoutInflater =(LayoutInflater) context.getSystemService

(Context.LAYOUT_INFLATER_SERVICE);

在WindowManager调用addView方法时,切记不能重复添加,否则就要报错了,同时在service

关闭时,记得要removeView掉,否则容易内存泄漏。好,上代码

class CopyOfShowAddressService extends Service {
protected static final String TAG = "ShowAddressService";

private View view;

// 获取到手机的窗体管理器
private WindowManager wm;

@Override
public IBinder onBind(Intent intent) {
return null;
}

private WindowManager.LayoutParams params;

/**
* 自定义土司 把一个view对象显示到手机窗体上

* @param address
*/
public void showAddress(String address) {
view = View.inflate(getApplicationContext(), 

R.layout.toast_location,
null);
// 给窗体上的view对象注册触摸事件
view.setOnTouchListener(new OnTouchListener() {
int startX;
int startY;

@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "呼叫界面,更改位置+摸到");
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "呼叫界面,更改位置+移动");
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();

int dx = newX - startX;
int dy = newY - startY;

// 更改view对象在窗体上显示的位置.
params.x += dx;
params.y += dy;

if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y = 0;
}

if (params.x > wm.getDefaultDisplay

().getWidth()) {
params.x = wm.getDefaultDisplay

().getWidth();
}

if (params.y > wm.getDefaultDisplay

().getHeight()) {
params.y = wm.getDefaultDisplay

().getHeight();
}
wm.updateViewLayout(view, params);

// 重新初始化手指的位置
startX = (int) event.getRawX();
startY = (int) event.getRawY();

break;
case MotionEvent.ACTION_UP:
int lastx = params.x;
int lasty = params.y;
SharedPreferences sp = 

getSharedPreferences("config",
Context.MODE_PRIVATE);
Editor editor = sp.edit();
editor.putInt("lastx", lastx);
editor.putInt("lasty", lasty);
editor.commit();
break;
}

return true;
}
});

TextView tv = (TextView) view.findViewById

(R.id.tv_toast_address);
tv.setText(address);

SharedPreferences sp = getSharedPreferences("config", 

MODE_PRIVATE);
params = new WindowManager.LayoutParams();
params.gravity = Gravity.TOP | Gravity.LEFT;
int lastx = sp.getInt("lastx", 0);
int lasty = sp.getInt("lasty", 0);
params.x = lastx;
params.y = lasty;

params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
// 定义控件 可以触摸 删除一个flag
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
// 定义窗体的类型 TYPE_PRIORITY_PHONE
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
wm.addView(view, params);

}

@Override
public void onCreate() {
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

showAddress("");
super.onCreate();
}

@Override
public void onDestroy() {

if (view != null) {
wm.removeView(view);
view = null;
}
super.onDestroy();

}

}

代码里有注释,已经很清楚了。该控件如果需要点击事件,直接注册

view.setOnClickListener()即可,同时注意把view.setOnTouchListener()中的返回值改为

false,因为注册了点击事件,就相当于触摸事件消费了,如果还是设置为true,则点击事件

无效,不会被执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息