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

项目里面,有时会碰到需求,要求一个小图标或者布局悬浮于窗体上,像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,则点击事件


无效,不会被执行。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您介绍一下Android仿IOS自定义AlertDialog提示框的实现方法。 首先,我们需要在Android项目中创建一个自定义布局文件,用于显示弹框的内容。可以使用LinearLayout或RelativeLayout等布局容器来组织弹框的内容,例如: ```xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="标题" android:textSize="18sp" /> <TextView android:id="@+id/message" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="内容" android:textSize="14sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center"> <Button android:id="@+id/confirm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="确定" /> <Button android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="取消" /> </LinearLayout> </LinearLayout> ``` 接下来,我们需要创建一个自定义AlertDialog类,用于显示弹框和处理按钮点击事件。在这个类中,我们需要实现onCreateDialog方法来加载自定义布局文件,并设置弹框的标题、内容和按钮监听器等。例如: ```java public class IOSAlertDialog extends DialogFragment { private String title; private String message; private DialogInterface.OnClickListener confirmListener; private DialogInterface.OnClickListener cancelListener; public IOSAlertDialog(String title, String message, DialogInterface.OnClickListener confirmListener, DialogInterface.OnClickListener cancelListener) { this.title = title; this.message = message; this.confirmListener = confirmListener; this.cancelListener = cancelListener; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = LayoutInflater.from(getActivity()); View view = inflater.inflate(R.layout.dialog_ios_alert, null); TextView titleView = view.findViewById(R.id.title); TextView messageView = view.findViewById(R.id.message); Button confirmButton = view.findViewById(R.id.confirm); Button cancelButton = view.findViewById(R.id.cancel); titleView.setText(title); messageView.setText(message); confirmButton.setOnClickListener(confirmListener); cancelButton.setOnClickListener(cancelListener); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(view); return builder.create(); } } ``` 最后,在我们的Activity中,我们可以通过创建一个实例对象,并调用show方法来显示弹框。例如: ```java IOSAlertDialog dialog = new IOSAlertDialog( "提示", "确定要删除吗?", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 确定按钮点击事件 } }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 取消按钮点击事件 } }); dialog.show(getSupportFragmentManager(), "IOSAlertDialog"); ``` 这样,我们就可以实现一个Android仿IOS自定义AlertDialog提示框了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值