android如何实现类似ios点击状态栏回到顶部功能

安卓app内部实现一个全局的悬浮按钮,类似于360的悬浮球可在app内部所有界面显示。主要还是来源于网上搜索的,经过自己整合和修改!效果如图,主要是一个工具类,

在调试时间敏感的程序时,实时在屏幕上打印当前系统时间非常有用,下面就是利用Andorid悬浮窗口制作的一个小工具,在屏幕上实时显示当前实际,此文件的基本版本来源于

CSDN博客地址:

代码如下:

直接贴代码,大家有疑问直接留言:

由于公司的项目基本上都是以ios为主,android为辅的,因此开需求会议的时候,经常会碰到“要实现点击状态栏回到顶部”的需求,这个功能ios实现起来特简单,也就一个属性的问题,但是android实现起来却超麻烦(所以当初不知道是基于什么原因,老大基本上都是把这个功能推掉的),最近发现微信朋友圈也具有类似ios点击状态栏回到顶部的功能,也许你们会说我肯定是点错了,点到标题栏或者直接认为我状态栏、标题栏不分,但是,我可以在这里很认真、很负责地告诉大家我没点错也没有状态栏、标题栏都不分,只不过点击状态栏回到顶部这个功能貌似是跟手机系统有关,我在坚果(5.0)手机上试了下有这个功能,在红米1s(4.4.2)和联想手机上都不行,但是这丝毫不影响我的求知欲望,因为这篇文章或许是第一篇讲解如何实现类似ios点击状态栏回到顶部的技术文章。

import android.content.Context;

启动窗口,接受参数按不同的时间间隔显示时间:

一开始的时候,我以为很简单,因为先前接触沉浸式状态栏的时候知道可以给状态栏添加一个View,然后再通过给这个View添加一系列的属性或者事件什么的,但是真正动手实现起来的时候却发现了一个很神奇的问题,在对该View添加背影色,添加文字内容什么的都能显示出来,但是我一在状态栏上下拉时,那个View就不见了,当我再点击内容区域时,那个View又出来了,当时我的内心是十分崩溃的,于是在经过一系列的检查和尝试依旧没有找到问题所在后,果断采用了另一种方法就是通过给状态栏添加一个悬浮窗,对,没错就是悬浮窗,于是乎在确定方案后就翻阅起API文档来,因为要想把悬浮窗放在状态栏上面就得给Window设置一系列的属性,另外需要注意的是,在小米手机上需要自己手动在安全中心上打开悬浮窗的权限,如果其它手机没出现悬浮窗的话估计也是权限的问题,在相应地方打开权限就好,实现效果如下所示:

import android.graphics.Color;

package com.bely.debugassist;

要想实现一个悬浮窗效果需要借助WindowManager类,该WindowManager提供了3个用来操作视图的类,addView(View
view,LayoutParams params),updateViewLayout(View view,LayoutParams
params),及removeView(View
view),其作用如其方法名一样分别是用来增加、更新及移除View。其次,我们还需要借助WindowManager的LayoutParams来为悬浮窗添加一系列的属性,如type、flags、format等等。

import android.graphics.PixelFormat;

import android.app.AppOpsManager;

1、可以通过如下代码获取WindowManager:

import android.view.Gravity;

import android.content.pm.PackageManager;

WindowManager windowManager = (WindowManager)
getSystemService(WINDOW_SERVICE);

import android.view.LayoutInflater;

import android.content.Intent;

2、获取WindowManager的LayoutParams并设置一系列属性:

import android.view.MotionEvent;

import android.os.Build;

WindowManager.LayoutParams params = new WindowManager.LayoutParams();

import android.view.View;

import android.provider.Settings;

//设置window type
下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上

import android.view.ViewGroup;

import android.app.Activity;

params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;

import android.view.WindowManager;

import android.os.Bundle;

//设置图片格式,效果为背景透明

import com.mocoo.ezc.R;

import android.widget.Toast;

params.format = PixelFormat.RGBA_8888;

import com.mocoo.ezc.utils.Log;

import android.os.UserHandle;

//设置可以显示在状态栏上,flags值须大于1280时,悬浮窗才会在状态栏之上

import com.mocoo.ezc.utils.SPUtil;

public class MainActivity extends Activity {

params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |

WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |

WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;

public class FloatView2 {

    @Override

//设置悬浮窗口长宽数据

private Context c;

    protected void onCreate(Bundle savedInstanceState) {

params.height = WindowManager.LayoutParams.WRAP_CONTENT;

params.width = WindowManager.LayoutParams.MATCH_PARENT;

private int height=0;

        super.onCreate(savedInstanceState);

// 悬浮窗默认显示以左上角为起始坐标

private int width=0;

        Intent startIntent = getIntent();

params.gravity = Gravity.LEFT | Gravity.TOP;

public FloatView2(Context c) {

        if (startIntent != null) {

3、设置完属性后就可以加载悬浮窗需要显示的内容了,如下:

this.c=c;

            int interval = startIntent.getIntExtra(Config.KEY_INTERVAL,
Config.interval);

View view = LayoutInflater.from(this).inflate(R.layout.view_window,
null);

//获取子控件

TextView tv_statusBarView = (TextView)
view.findViewById(R.id.tv_statusBarView);

//动态将子控件的高度设置成状态栏的高度

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)
tv_statusBarView.getLayoutParams();

layoutParams.height =
StatusBarUtils.getStatusBarHeight(getApplicationContext());

tv_statusBarView.setLayoutParams(layoutParams);

}

            Config.interval = interval;

此处加载的view_window布局文件的代码如下所示:

private WindowManager wm;

        }

<LinearLayout
xmlns:android=”;

android:layout_width=”match_parent”

android:layout_height=”match_parent”

android:background=”#00000000″

android:descendantFocusability=”blocksDescendants”

android:orientation=”vertical”>

<TextView

android:id=”@+id/tv_statusBarView”

android:layout_width=”match_parent”

android:layout_height=”50dp”

android:background=”#00000000″

android:clickable=”true”

android:enabled=”true”

android:gravity=”center”/>

</LinearLayout>

private View view;// 浮动按钮

        AppOpsManager appOps =
this.getSystemService(AppOpsManager.class);

获取状态栏高度的getStatusBarHeight方法如下所示:

WindowManager.LayoutParams params;

        PackageManager mPackageManager = this.getPackageManager();

/**

* 获得状态栏高度

*/

public static int getStatusBarHeight(Context context) {

int resourceId =
context.getResources().getIdentifier(“status_bar_height”, “dimen”,
“android”);

return context.getResources().getDimensionPixelSize(resourceId);

}

/**

        int uid = 0;

最后需要把加载的View通过WindowManager的addView方法添加到状态栏上:

* 添加悬浮View

        try{

windowManager.addView(view, params);

* @param

            uid = mPackageManager.getPackageUid(“com.debugassist”,
UserHandle.USER_ALL);

此时,悬浮窗就已经被添加在状态栏上了,但是此时点击状态栏是没有任何响应的,因此悬浮窗位于状态栏上并且把状态栏的一系列事件都给拦截了,因此,需要我们为悬浮窗添加一个触摸事件,也许大家会问,为什么是触摸事件而不是点击事件呢?因为如下上面所说悬浮窗把状态栏的事件都给拦截了,因此需要在触摸事件中计算滑动的距离来判断是点击悬浮窗呢还是让状态栏展开呢,这里的让状态栏展开是通过反射的方式来操作的,下面会讲到,另外我们还需要借助GestureDetector来进行手势操作。

*/

        }catch(PackageManager.NameNotFoundException e) {

1、首先,需要定义一个手势监听器CustomOnGustureListener并让其继承自SimpleOnGestureListener并实现其中的onSingleTapConfirmed方法,代码如下所示:此时,悬浮窗就已经被添加在状态栏上了,但是此时点击状态栏是没有任何响应的,因此悬浮窗位于状态栏上并且把状态栏的一系列事件都给拦截了,因此,需要我们为悬浮窗添加一个触摸事件,也许大家会问,为什么是触摸事件而不是点击事件呢?因为如下上面所说悬浮窗把状态栏的事件都给拦截了,因此需要在触摸事件中计算滑动的距离来判断是点击悬浮窗呢还是让状态栏展开呢,这里的让状态栏展开是通过反射的方式来操作的,下面会讲到,另外我们还需要借助GestureDetector来进行手势操作。

public void createFloatView() {

        }

/**

* 自定义手势监听器

*/

private class CustomOnGustureListener extends
GestureDetector.SimpleOnGestureListener {

@Override

public boolean onSingleTapConfirmed(MotionEvent e) {

//如果isMove不为true表示是点击事件

if (!isMove) {

Toast.makeText(getApplicationContext(), “你点击了悬浮窗”,
Toast.LENGTH_SHORT).show();

Log.i(“test”, “onClick”);

if(onStatusBarClickListener!=null){

onStatusBarClickListener.onClick();

}

}

return super.onSingleTapConfirmed(e);

}

}

wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);

        appOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,

2、其次,还需要定义一个OnFloatingListener类让其实现OnTouchListener接口,然后覆写其onTouchEvent方法,最后并将其交给GetstureDetector处理,代码如下所示:

height = wm.getDefaultDisplay().getHeight();

                uid, “com.debugassist”, AppOpsManager.MODE_ALLOWED);

//分别用于记录按下,移动、抬起时相应的x、y坐标

private int startX, startY, moveX, moveY, stopX, stopY;

private int offsetX, offsetY;

//用于标记悬浮窗是否有移动

private boolean isMove;

/**

*
由于悬浮窗是位于状态栏之上且覆盖状态栏的焦点以至于状态栏的相应事件失效,如:下拉出通知

* 因此需要通过监听悬浮窗在不同状态下触发相应的事件

*/

private class OnFloatingListener implements View.OnTouchListener {

@Override

public boolean onTouch(View v, MotionEvent event) {

int action = event.getAction();

switch (action) {

case MotionEvent.ACTION_DOWN:

isMove = false;

startX = (int) event.getX();

startY = (int) event.getY();

break;

case MotionEvent.ACTION_MOVE:

moveX = (int) event.getX();

moveY = (int) event.getY();

offsetY = Math.abs(startY – moveY);

//当移动距离大于某个值时,表示是在下拉状态栏,此时展开状态栏

if (Math.abs(offsetY) >= 8) {

StatusBarUtils.expandStatusBar(getApplicationContext());

}

break;

case MotionEvent.ACTION_UP:

stopX = (int) event.getX();

stopY = (int) event.getY();

offsetY = Math.abs(startY – stopY);

//如果手抬起时移动的距离大于某个值,表示是处于下拉操作

if (Math.abs(offsetY) >= 8) {

isMove = true;

}

break;

}

return
gestureDetector.onTouchEvent(event);//将onTouchEvent交给GestureDetector处理

}

}

width = wm.getDefaultDisplay().getWidth();

        if (Settings.canDrawOverlays(MainActivity.this))

最后就是初始化GestureDetector和绑定触摸事件了,代码如下所示:

view = LayoutInflater.from.inflate(R.layout.floatview, null);

        {

GestureDetector gestureDetector = new GestureDetector(this, new
CustomOnGustureListener());

tv_statusBarView.setOnTouchListener(new OnFloatingListener());

params = new WindowManager.LayoutParams();

            Intent intent = new
Intent(MainActivity.this,MainService.class);

当然为了理方便的使用,我将上面所有代码都放在一个Service中,并且还为其提供了一个当点击状态栏时的回调,代码如下所示:

params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//
所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。

            Toast.makeText(MainActivity.this,”DebugAssist
enabled.”,Toast.LENGTH_SHORT).show();

private OnStatusBarClickListener onStatusBarClickListener;

public void setOnStatusBarClickListener(OnStatusBarClickListener
onStatusBarClickListener) {

this.onStatusBarClickListener = onStatusBarClickListener;

}

public interface OnStatusBarClickListener{

void onClick();

}

params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL

            startService(intent);

此时,关于如何实现类似ios点击状态栏回到顶部的主要功能就已经全部实现了,为了更方便的使用,我将启动悬浮窗的代码的放在BaseActivity中,代码如下所示:

| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

            finish();

package abner.clickstatusbar2top.activities;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.os.Bundle;

import android.os.IBinder;

import android.support.annotation.Nullable;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.Window;

import abner.clickstatusbar2top.service.FloatingService;

/**

* Created by abner on 2016/7/24.

*/

public abstract class BaseActivity extends AppCompatActivity {

private Intent intent;

protected FloatingService floatingService;

private ServiceConnection connection;

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

super.onCreate(savedInstanceState);

//        initServiceConnection();

startFloatingService();

//        setListener();

}

private void initServiceConnection() {

connection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

Log.i(“test”, “onServiceConnected”);

floatingService = ((FloatingService.SubFloatingService)
service).getService();

if (floatingService != null) {

setListener();

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

}

};

}

protected abstract void setListener();

@Override

protected void onPause() {

super.onPause();

stopFloatingService();

}

private void startFloatingService() {

intent = new Intent(this, FloatingService.class);

//        startService(intent);

if (connection == null) {

initServiceConnection();

}

bindService(intent, connection, Context.BIND_AUTO_CREATE);

}

private void stopFloatingService() {

if (connection != null) {

//            stopService(intent);

unbindService(connection);

connection = null;

}

}

@Override

public void onWindowFocusChanged(boolean hasFocus) {

super.onWindowFocusChanged(hasFocus);

if (hasFocus) {

startFloatingService();

} else {

stopFloatingService();

}

}

}

params.format = PixelFormat.TRANSLUCENT;//
不设置这个弹出框的透明遮罩显示为黑色

        }else

在代码的最后可以发现,添加了一个onWindowFocusChanged方法,主要是为了解决当下拉状态栏时,再点击状态栏时也会响应点击事件,由于下拉状态栏时当前Activity处于不可见状态,因此可以通过onWindowFocusChanged方法进行判断。

params.type = WindowManager.LayoutParams.TYPE_TOAST;

        {

在使用时,我们只需要将我们的Activity都继承自BaseActivity,然后在setListener方法中调用setOnStatusBarClickListener方法并在其回调中让列表回到顶部即可实现点击状态栏回到顶部功能,如:

params.width = WindowManager.LayoutParams.WRAP_CONTENT;

            Toast.makeText(MainActivity.this,”Some error happens, cannot
start.”,Toast.LENGTH_SHORT).show();

package abner.clickstatusbar2top.activities;

import android.os.Bundle;

import android.util.Log;

import android.widget.ListView;

import java.util.ArrayList;

import java.util.List;

import abner.clickstatusbar2top.R;

import abner.clickstatusbar2top.adapter.NewsAdapter;

import abner.clickstatusbar2top.bean.News;

import abner.clickstatusbar2top.service.FloatingService;

public class MainActivity extends BaseActivity {

private ListView lv_content;

private List datas;

private NewsAdapter adapter;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

lv_content = (ListView) findViewById(R.id.lv_content);

initData();

}

@Override

protected void setListener() {

floatingService.setOnStatusBarClickListener(new
FloatingService.OnStatusBarClickListener() {

@Override

public void onClick() {

Log.i(“test”,”setListener”);

lv_content.smoothScrollToPosition(0);

}

});

}

private void initData() {

datas = new ArrayList<>();

News news;

for (int i = 0; i < 50; i++) {

news = new News();

news.setTitle(“这是标题:”+i);

news.setDesc(“这是描述信息:”+i);

news.setDate(“这是时间”+i);

datas.add(news);

}

adapter = new NewsAdapter(this,datas);

lv_content.setAdapter(adapter);

}

}

params.height = WindowManager.LayoutParams.WRAP_CONTENT;

            finish();

源码

params.gravity = Gravity.TOP | Gravity.LEFT;

        }

int screenWidth = c.getResources().getDisplayMetrics().widthPixels;

    }

int screenHeight = c.getResources().getDisplayMetrics().heightPixels;

}

params.y = screenHeight- height/3;//设置距离底部高度为屏幕三分之一

Service 程序,负责显示时间

params.x = screenWidth;

package com.bely.debugassist;

view.setBackgroundColor(Color.TRANSPARENT);

import java.text.SimpleDateFormat;

view.setVisibility(View.VISIBLE);

import java.util.Date;

view.setOnTouchListener(new View.OnTouchListener() {

import android.app.Service;

// 触屏监听

import android.content.Context;

float lastX, lastY;

import android.content.Intent;

int oldOffsetX, oldOffsetY;

import android.graphics.PixelFormat;

int tag = 0;// 悬浮球 所需成员变量

import android.os.Handler;

@Override

import android.os.IBinder;

public boolean onTouch(View v, MotionEvent event) {

import android.os.Message;

final int action = event.getAction();

import android.os.SystemClock;

float x = event.getX();

import android.util.Log;

float y = event.getY();

import android.view.Gravity;

if {

import android.view.LayoutInflater;

oldOffsetX = params.x; // 偏移量

import android.view.MotionEvent;

oldOffsetY = params.y; // 偏移量

import android.view.View;

}

import android.view.WindowManager;

if (action == MotionEvent.ACTION_DOWN) {

import android.widget.ImageButton;

lastX = x;

import android.widget.LinearLayout;

lastY = y;

import android.widget.RelativeLayout;

} else if (action == MotionEvent.ACTION_MOVE) {

import android.widget.TextView;

params.x += (x – lastX) / 3; // 减小偏移量,防止过度抖动

import android.widget.Toast;

params.y += (y – lastY) / 3; // 减小偏移量,防止过度抖动

public class MainService extends Service {

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图