在实际的项目中,会在很多情况下需要在界面添加或弹出窗口,同时又希望这些视图不会对原本的布局产生影响,是独立的,那么,针对于各种需求之下,我们应该选择怎样的实现方式呢,我自己进行了一个初步的整理
通常来说,我们对添加的独立视图的重点主要是以下几点:
1、产生作用的范围,是只在当前界面显示,还是不跟随当前界面显示
2、作用的方式,是显示后不让其他视图可以被操作,还是显示的同时不会影响其他的正常操作
3、显示的方式,是希望围绕特定的view显示,还是可以独立在任何坐标显示
4、作用的生命周期,是只有较短的生命周期,还是希望可以控制其生命周期任意长短
那么围绕以上几点,我思考并整理了一下各种情况下应该使用什么,以及其一些比较基础的写法
分类一、只在当前界面显示
这种情况下,实现的方式有两种比较适合,一种是通过addview的方式实现,一种是通过dialog的方式实现
而这两种方式又有不同,addview不会影响其他视图的操作,而dialog会影响其他view的操作甚至显示效果
addview可以全屏,而dialog则很难全屏
那么,addview的实现方式是怎样的呢:
public class MainActivity extends Activity implements OnTouchListener{
ViewGroup mGroup;
Button mButton;
private int _xDelta;
<span style="white-space:pre"> </span>private int _yDelta;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGroup = (ViewGroup)findViewById(R.id.viewgroup);
mButton = (Button)findViewById(R.id.id_text);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
150, 150);
layoutParams.leftMargin = 0;
layoutParams.topMargin = 0;
mLayout = LayoutInflater.from(this).inflate(R.layout.layout_test,null);
mLayout.setBackgroundColor(Color.BLUE);
mButton.setLayoutParams(layoutParams);
mButton.setOnTouchListener(this);
mLayout.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
final int X = (int) event.getRawX();
final int Y = (int) event.getRawY();
// 使用switch (event.getAction() & MotionEvent.ACTION_MASK)
// 就可以处理处理多点触摸的ACTION_POINTER_DOWN和ACTION_POINTER_UP事件。
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) v
.getLayoutParams();
_xDelta = X - lParams.leftMargin;
_yDelta = Y - lParams.topMargin;
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_POINTER_UP:
if(mGroup.getChildCount()<2){
RelativeLayout.LayoutParams layoutParamss = new RelativeLayout.LayoutParams(
300, 300);
layoutParamss.leftMargin = 100;
layoutParamss.topMargin = 100;
mGroup.addView(mLayout,layoutParamss);
mLayout.invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v
.getLayoutParams();
layoutParams.leftMargin = X - _xDelta;
layoutParams.topMargin = Y - _yDelta;
// layoutParams.rightMargin = -250;
// layoutParams.bottomMargin = -250;
v.setLayoutParams(layoutParams);
break;
}
mButton.invalidate();
return true;
}
}
上面这个例子中,是实现了addview的方式,可以看到,对于新添加的view的位置等参数控制主要通过
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v
.getLayoutParams();
layoutParams.leftMargin = X - _xDelta;
layoutParams.topMargin = Y - _yDelta;
v.setLayoutParams(layoutParams);
这种方式来实现
那么接下来的就是dialog
package angel.devil;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
public class DialogDemoActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Dialog dialog = new Dialog(this);
// setContentView可以设置为一个View也可以简单地指定资源ID
// LayoutInflater
// li=(LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
// View v=li.inflate(R.layout.dialog_layout, null);
// dialog.setContentView(v);
dialog.setContentView(R.layout.dialog_layout);
dialog.setTitle("Custom Dialog");
/*
* 获取圣诞框的窗口对象及参数对象以修改对话框的布局设置,
* 可以直接调用getWindow(),表示获得这个Activity的Window
* 对象,这样这可以以同样的方式改变这个Activity的属性.
*/
Window dialogWindow = dialog.getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
dialogWindow.setGravity(Gravity.LEFT | Gravity.TOP);
/*
* lp.x与lp.y表示相对于原始位置的偏移.
* 当参数值包含Gravity.LEFT时,对话框出现在左边,所以lp.x就表示相对左边的偏移,负值忽略.
* 当参数值包含Gravity.RIGHT时,对话框出现在右边,所以lp.x就表示相对右边的偏移,负值忽略.
* 当参数值包含Gravity.TOP时,对话框出现在上边,所以lp.y就表示相对上边的偏移,负值忽略.
* 当参数值包含Gravity.BOTTOM时,对话框出现在下边,所以lp.y就表示相对下边的偏移,负值忽略.
* 当参数值包含Gravity.CENTER_HORIZONTAL时
* ,对话框水平居中,所以lp.x就表示在水平居中的位置移动lp.x像素,正值向右移动,负值向左移动.
* 当参数值包含Gravity.CENTER_VERTICAL时
* ,对话框垂直居中,所以lp.y就表示在垂直居中的位置移动lp.y像素,正值向右移动,负值向左移动.
* gravity的默认值为Gravity.CENTER,即Gravity.CENTER_HORIZONTAL |
* Gravity.CENTER_VERTICAL.
*
* 本来setGravity的参数值为Gravity.LEFT | Gravity.TOP时对话框应出现在程序的左上角,但在
* 我手机上测试时发现距左边与上边都有一小段距离,而且垂直坐标把程序标题栏也计算在内了,
* Gravity.LEFT, Gravity.TOP, Gravity.BOTTOM与Gravity.RIGHT都是如此,据边界有一小段距离
*/
lp.x = 100; // 新位置X坐标
lp.y = 100; // 新位置Y坐标
lp.width = 300; // 宽度
lp.height = 300; // 高度
lp.alpha = 0.7f; // 透明度
// 当Window的Attributes改变时系统会调用此函数,可以直接调用以应用上面对窗口参数的更改,也可以用setAttributes
// dialog.onWindowAttributesChanged(lp);
dialogWindow.setAttributes(lp);
/*
* 将对话框的大小按屏幕大小的百分比设置
*/
// WindowManager m = getWindowManager();
// Display d = m.getDefaultDisplay(); // 获取屏幕宽、高用
// WindowManager.LayoutParams p = dialogWindow.getAttributes(); // 获取对话框当前的参数值
// p.height = (int) (d.getHeight() * 0.6); // 高度设置为屏幕的0.6
// p.width = (int) (d.getWidth() * 0.65); // 宽度设置为屏幕的0.65
// dialogWindow.setAttributes(p);
dialog.show();
}
}
调用方式也是比较简单的
分类二、作用的方式
这一点主要由是否会影响其他视图的操作来区分,这种方式的话,影响其他视图操作的主要是dialog
而不影响其他视图操作的,则是addview方式和WindowManager(桌面悬浮窗)的方式
分类三、显示的方式,是自由地显示,还是环绕某个view显示
这里,自由的显示的很多,而环绕某个view显示的则实现比较方便的是popWindow
实现方式为:
public class PopwindowLeftNew extends Activity{
private PopupWindow popupWindow;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popupwindow_main);
findViewById(R.id.popBtn).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// 获取自定义布局文件activity_popupwindow_left.xml的视图
View popupWindow_view = getLayoutInflater().inflate(R.layout.activity_popupwindow_left, null,false);
// 创建PopupWindow实例,200,LayoutParams.MATCH_PARENT分别是宽度和高度
popupWindow = new PopupWindow(popupWindow_view, 200, LayoutParams.MATCH_PARENT, true);
// 设置动画效果
popupWindow.setAnimationStyle(R.style.AnimationFade);
// 这里是位置显示方式,在屏幕的左侧
popupWindow.showAtLocation(v, Gravity.LEFT, 0, 0);
// 点击其他地方消失
popupWindow_view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.dismiss();
popupWindow = null;
}
return false;
}
});
}
});
}
}
这种方式则需要有依靠的view方可显示,这种方式需要注意的是,如果在视图还没有显示时,去获取
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v
.getLayoutParams();
这种视图参数时,是会获取到为null的如此时对其参数进行设置则会报错,需要注意,最好是在视图已显示时,对其进行添加和调整参数
分类四、生命周期的长短
对于上面已介绍的方式来说,都是跟随着Activity和View的,当其依附的视图不显示时,则也会跟着但是还有一种方式,其弹出窗口的生命周期是可以跟着Activity或者application的生命周期的,就是类似360的桌面精灵,这种方式相对来说比较灵活
实现方式为:
public class MainActivity extends Activity implements OnTouchListener{
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>Button mAdd;
WindowManager mWindowManager;
WindowManager.LayoutParams wmParams;
View mWindowView;
float mTouchStartX ;
float mTouchStartY ;
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>@Override
<span style="white-space:pre"> </span>protected void onCreate(Bundle savedInstanceState) {
<span style="white-space:pre"> </span>super.onCreate(savedInstanceState);
<span style="white-space:pre"> </span>setContentView(R.layout.activity_main);
<span style="white-space:pre"> </span>mWindowView = LayoutInflater.from(this).inflate(R.layout.layout_test,null);
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
150, 150);
layoutParams.leftMargin = 0;
layoutParams.topMargin = 0;
mAdd = (Button)findViewById(R.id.id_add);
initClickListener();
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>private void initClickListener(){
<span style="white-space:pre"> </span> mAdd.setOnClickListener(new OnClickListener() {
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>@Override
<span style="white-space:pre"> </span>public void onClick(View v) {
<span style="white-space:pre"> </span>//获取的是LocalWindowManager对象
<span style="white-space:pre"> </span>mWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
//<span style="white-space:pre"> </span>mWindowManager = MainActivity.this.getWindowManager();
<span style="white-space:pre"> </span>//获取LayoutParams对象
<span style="white-space:pre"> </span>wmParams = new WindowManager.LayoutParams();
<span style="white-space:pre"> </span>//设置window type
<span style="white-space:pre"> </span>wmParams.type = LayoutParams.TYPE_PHONE;
<span style="white-space:pre"> </span>//设置图片格式,效果为背景透明
<span style="white-space:pre"> </span> wmParams.format = PixelFormat.RGBA_8888;
<span style="white-space:pre"> </span> //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
<span style="white-space:pre"> </span> wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
<span style="white-space:pre"> </span> //调整悬浮窗显示的停靠位置为左侧置顶
<span style="white-space:pre"> </span> wmParams.gravity = Gravity.LEFT | Gravity.TOP;
<span style="white-space:pre"> </span> // 以屏幕左上角为原点,设置x、y初始值,相对于gravity
<span style="white-space:pre"> </span> wmParams.x = 0;
<span style="white-space:pre"> </span> wmParams.y = 0;
<span style="white-space:pre"> </span> wmParams.width = 300;
<span style="white-space:pre"> </span> wmParams.height = 300;
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> //这里指在控件未初始化显示时设置待会可以获取实际初始化时的长宽
<span style="white-space:pre"> </span> mWindowView.measure(View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED)
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>, View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED));
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> mWindowManager.addView(mWindowView, wmParams);
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>});
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> mWindowView.setOnTouchListener(new OnTouchListener() {
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>@Override
<span style="white-space:pre"> </span>public boolean onTouch(View v, MotionEvent event) {
<span style="white-space:pre"> </span>switch (event.getAction()) {
<span style="white-space:pre"> </span> case MotionEvent.ACTION_DOWN:
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//获取相对View的坐标,即以此View左上角为原点
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>mTouchStartX = event.getX();
<span style="white-space:pre"> </span> mTouchStartY = event.getY();
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> break;
<span style="white-space:pre"> </span> case MotionEvent.ACTION_MOVE:<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>wmParams.x = (int) (event.getRawX() - mTouchStartX);
<span style="white-space:pre"> </span>wmParams.y = (int) (event.getRawY() - mTouchStartY);
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>mWindowManager.updateViewLayout(mWindowView, wmParams);
<span style="white-space:pre"> </span> break;
<span style="white-space:pre"> </span> case MotionEvent.ACTION_UP:
//<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>updateViewPosition();
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>mTouchStartX=mTouchStartY=0;
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span> }
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>return false;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>});
<span style="white-space:pre"> </span>}
}
这种悬浮窗开始比较灵活的,它的创建方式,有两种,一种是跟随创建的activity的,另一种是跟随application的,比较长
跟随application的创建方式为:
mWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
而跟随activity的生命周期的方式为:
mWindowManager = MainActivity.this.getWindowManager();
那么,最后整理一下各种实现方式的特性
addview方式:不影响其他视图的操作、添加位置、时机比较灵活,但是很难围绕特性view添加,生命周期跟随添加的groupview
WindowManager方式:不影响其他视图的操作,添加位置、时机比较灵活,但是很难围绕特性view添加,生命周期可以跟随activity或者application
popWindow方式:不影响其他视图操作,添加位置局限于特定view,添加时机最好在对应view已显示之后,生命周期由代码控制,但是不会在activity结束之后还能存活
Dialog方式:影响其他视图操作,影响其他视图视觉效果,添加位置可以定制,但是不可全屏,添加时机最好在activity显示之后,生命周期一般仅限于自己显示到不显示
以后遇到其他场景时再进行扩展
今天看到了文章
http://blog.csdn.net/lmj623565791/article/details/23116115
又了解到了一种让Activity显示为类似弹出窗口出现的方式,主要实现方式为:
在Activity在manifest生命的地方加上
android:theme="@style/MyDialogTopRight"
其中的配置为:
<style name="MyDialogTopRight">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@style/Anim_scale</item>
</style>
最后一个为动画效果
<style name="Anim_scale" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/scale_in</item>
<item name="android:activityOpenExitAnimation">@anim/scale_out</item>
<item name="android:activityCloseEnterAnimation">@anim/scale_in</item>
<item name="android:activityCloseExitAnimation">@anim/scale_out</item>
</style>
其实原理很简单,就是把Activity的背景设置为完全透明,并且没有标题的样子,这样就会造成类似于弹出窗口的样子
这种方式的确很有意思,一般情况下的确也能满足一点需求,只是这种方式还是存在一定缺陷的
在需要弹出窗口弹出和收回的效果需要依托于某个组件来设置动画时,这种方式将会很麻烦
不过还是在这里记录一下,扩展一下思路