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

CSDN博客地址:http://blog.csdn.net/zhangjinhuang/article/details/52015020

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

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

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

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

WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

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

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

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

params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;

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

params.format = PixelFormat.RGBA_8888;

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

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;

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

params.height = WindowManager.LayoutParams.WRAP_CONTENT;

params.width = WindowManager.LayoutParams.MATCH_PARENT;

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

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

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

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);

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/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>

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

/**

* 获得状态栏高度

*/

public static int getStatusBarHeight(Context context) {

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

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

}

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


windowManager.addView(view, params);

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

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

/**

* 自定义手势监听器

*/

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);

}

}

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

//分别用于记录按下,移动、抬起时相应的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处理

}

}

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

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

tv_statusBarView.setOnTouchListener(new OnFloatingListener());

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

private OnStatusBarClickListener onStatusBarClickListener;

public void setOnStatusBarClickListener(OnStatusBarClickListener onStatusBarClickListener) {

this.onStatusBarClickListener = onStatusBarClickListener;

}

public interface OnStatusBarClickListener{

void onClick();

}

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

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();

}

}

}

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

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

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);

}

}

源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容