关于号码归属地显示的功能,还差的就是它的拖拽效果了。它在两个地方可以拖拽,一个是在提示框位置的设置界面,一个是在来电界面,如图所示。
关于项目相关文章,请访问:
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe
创建一个自定义 View,用于设置界面,作为设置提示框位置的二级入口菜单,自定义控件与前面讲过的自定义组合控件类似,只是做了一定的简化,代码如下:
/** * Created by XWdoor on 2016/3/1 001 16:19. * 博客:http://blog.csdn.net/xwdoor */
public class SettingClickView extends RelativeLayout {
public static final String NAMESPACE_XWDOOR = "http://schemas.android.com/apk/res-auto";
private TextView tvTitle;
private TextView tvDesc;
private CheckBox cbCheck;
public SettingClickView(Context context) {
super(context);
initView();
}
public SettingClickView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SettingClickView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView(){
View view = View.inflate(getContext(), R.layout.item_setting_click,null);
tvTitle = (TextView) view.findViewById(R.id.tv_title);
tvDesc = (TextView) view.findViewById(R.id.tv_desc);
addView(view);
}
/** 设置标题 */
public void setTitle(String title){
tvTitle.setText(title);
}
/** 设置描述 */
public void setDesc(String desc){
tvDesc.setText(desc);
}
}
它的布局文件我就贴出来了,与之前的 Item 惊人类似。然后在后台代码中设置它的标题与描述,代码如下:
scvLocation.setTitle("归属地提示框位置");
scvLocation.setDesc("设置归属地提示框位置");
scvLocation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DragViewActivity.startAct(SettingActivity.this);
}
});
该 Activity 用于实现拖拽效果,并作为归属地提示框的显示位置,效果如图:
这是它的布局文件:
<?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" android:background="#0a000000">
<TextView android:id="@+id/tv_top" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/call_locate_blue" android:gravity="center" android:padding="20dp" android:text="按住提示框拖动到任意位置,按手机返回键立即生效" android:textColor="#000" android:textSize="20sp" />
<TextView android:id="@+id/tv_bottom" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@drawable/call_locate_blue" android:gravity="center" android:padding="20dp" android:text="按住提示框拖动到任意位置,按手机返回键立即生效" android:textColor="#000" android:textSize="20sp" android:visibility="invisible" />
<TextView android:id="@+id/tv_drag_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="190dp" android:background="@drawable/call_locate_white" android:drawableLeft="@android:drawable/ic_menu_call" android:gravity="center" android:text="未知号码" android:textColor="#fff" android:textSize="18sp" />
</RelativeLayout>
后台代码如下,重点是提示框的触摸事件的处理逻辑:
/** * Created by XWdoor on 2016/3/15 015 10:02. * 博客:http://blog.csdn.net/xwdoor */
public class DragViewActivity extends BaseActivity {
private int startX;
private int startY;
private int mScreenWidth;
private int mScreenHeight;
public static void startAct(Context context) {
Intent intent = new Intent(context, DragViewActivity.class);
context.startActivity(intent);
}
@Override
public void initVariables() {
//得到屏幕宽高度
WindowManager wm = getWindowManager();
Point size = new Point();
wm.getDefaultDisplay().getSize(size);
mScreenWidth = size.x;
mScreenHeight = size.y;
//获取归属地提示框位置
startX = PrefUtils.getInt(PREF_LAST_X,0,this);
startY = PrefUtils.getInt(PREF_LAST_Y,0,this);
}
@Override
protected void initViews(Bundle savedInstanceState) {
setContentView(R.layout.activity_drag_view);
final TextView tvDragView = (TextView) findViewById(R.id.tv_drag_view);
final TextView tvTop = (TextView) findViewById(R.id.tv_top);
final TextView tvBottom = (TextView) findViewById(R.id.tv_bottom);
// 根据布局位置,更新提示框位置
if (startY > mScreenHeight / 2) {// 屏幕下方
tvTop.setVisibility(View.VISIBLE);
tvBottom.setVisibility(View.INVISIBLE);
} else {
tvTop.setVisibility(View.INVISIBLE);
tvBottom.setVisibility(View.VISIBLE);
}
// 获取当前控件的布局参数
// 父控件是谁,就拿谁定义的布局参数
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) tvDragView.getLayoutParams();
// 临时修改布局参数
layoutParams.leftMargin = startX;
layoutParams.topMargin = startY;
// 设置触摸监听
tvDragView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录起点坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 获取移动后坐标
int endX = (int) event.getRawX();
int endY = (int) event.getRawY();
// 计算偏移量
int dx = endX - startX;
int dy = endY - startY;
// 根据偏移量,更新位置
int l = tvDragView.getLeft() + dx;
int t = tvDragView.getTop() + dy;
int r = tvDragView.getRight() + dx;
int b = tvDragView.getBottom() + dy;
//避免布局超出屏幕边界
if (l >= 0 && r <= mScreenWidth && t >= 0 && b <= mScreenHeight - 20) {//减去状态栏,大概20像素
// 根据布局位置,更新提示框位置
if (t > mScreenHeight / 2) {// 屏幕下方
tvTop.setVisibility(View.VISIBLE);
tvBottom.setVisibility(View.INVISIBLE);
} else {
tvTop.setVisibility(View.INVISIBLE);
tvBottom.setVisibility(View.VISIBLE);
}
tvDragView.layout(l, t, r, b);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
}
break;
case MotionEvent.ACTION_UP:
//记录最终坐标
PrefUtils.putInt(PREF_LAST_X, tvDragView.getLeft(), DragViewActivity.this);
PrefUtils.putInt(PREF_LAST_Y, tvDragView.getTop(), DragViewActivity.this);
break;
}
return true;
}
});
}
@Override
protected void loadData() {
}
}
触摸事件中处理控件的移动偏移量,并移动控件,重要的是它的边界判断,超出边界后不处理它的移动,代码中都有很好的提示,没有多少难度。这里需要说明的是,在刚进入 Activity 时,要初始化上次保存的位置,这个时候不能直接使用 getWidth() 与 getHeight() 方法,因为这些方法只有在系统完成绘制之后才会返回有效值,而 measure(测量)->layout(设置位置)->onDraw(绘制), 这些方法必须在oncreate方法执行完了之后才会调用,所以我们只有获取相应的布局参数 LayoutParams,通过修改 LayoutParams 来达到初始化的目的。
来电时显示的归属地提示框也需要实现拖拽效果,它与 Activity 中实现还有一定区别,因为它显示在 WindowManager 中,这里主要给出触摸事件处理的代码,添加在 PhoneListener 中:
tvAddress.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录起点坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 获取移动后坐标
int endX = (int) event.getRawX();
int endY = (int) event.getRawY();
// 计算偏移量
int dx = endX - startX;
int dy = endY - startY;
// 根据偏移量,更新位置
// 根据偏移量,更新位置
params.x = params.x + dx;
params.y = params.y + dy;
// 防止坐标越界
if (params.x < 0) {
params.x = 0;
}
// 防止坐标越界
if (params.x + mView.getWidth() > mScreenWidth) {
params.x = mScreenWidth - mView.getWidth();
}
// 防止坐标越界
if (params.y < 0) {
params.y = 0;
}
// 防止坐标越界
if (params.y + mView.getHeight() > mScreenHeight - 20) {
params.y = mScreenHeight - 20 - mView.getHeight();
}
//减去状态栏,大概20像素
//根据布局位置,更新提示框位置
mWM.updateViewLayout(mView,params);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
//记录最终坐标
PrefUtils.putInt(BaseActivity.PREF_LAST_X, params.x, mContext);
PrefUtils.putInt(BaseActivity.PREF_LAST_Y, params.y, mContext);
break;
}
return true;
}
});
主要区别在于控件超出界面时的处理,以及跟新控件布局是调用的 api 为 WindowManager 的方法:updateViewLayout()。对了,要让 WindowManager 中的控件可触摸,需提高布局参数的类型级别:params.type = WindowManager.LayoutParams.TYPE_PHONE;
,这样才保证可以触摸移动。当然,这个是需要权限的:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
今天的任务算是上次的遗留问题,拖拽效果并不难实现,关键是一些细节的处理。关于自定义风格,代码就不给出了,具体实现可以下载源代码查看,主要思路是:不同风格就设置不同的背景图片。
关于项目相关文章,请访问:
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe