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

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

由于公司的项目基本上都是以ios为主,android为辅的,因此开需求会议的时候,经常会碰到“要实现点击状态栏回到顶部”的需求,这个功能ios实现起来特简单,也就一个属性的问题,但是android实现起来却超麻烦(所以当初不知道是基于什么原因,老大基本上都是把这个功能推掉的),最近发现微信朋友圈也具有类似ios点击状态栏回到顶部的功能,也许你们会说我肯定是点错了,点到标题栏或者直接认为我状态栏、标题栏不分,但是,我可以在这里很认真、很负责地告诉大家我没点错也没有状态栏、标题栏都不分,只不过点击状态栏回到顶部这个功能貌似是跟手机系统有关,我在坚果(5.0)手机上试了下有这个功能,在红米1s4.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。其次,我们还需要借助WindowManagerLayoutParams来为悬浮窗添加一系列的属性,如typeflagsformat等等。

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

WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

2、获取WindowManagerLayoutParams并设置一系列属性:

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布局文件的代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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通过WindowManageraddView方法添加到状态栏上:

//添加悬浮窗的视图
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;
    ServiceConnection 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) {

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        startFloatingService();
//        setListener();
    }

    protected abstract void setListener();

    @Override
    protected void onPause() {
        stopFloatingService();
        super.onPause();
    }

    private void startFloatingService() {
        intent = new Intent(this, FloatingService.class);
//        startService(intent);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);
    }

    private void stopFloatingService() {
        if (connection != null) {
//            stopService(intent);
            unbindService(connection);
        }
    }

    @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<News> 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);
    }



}

源码

 

 

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

我的相册管理系统 - 2016-07-26 04:07:10

功能文件的上传,下载和管理 技术:1.用xml当做数据库存储信息(dom4j,xpath) 2.Java表单的文件上传和下载 3.文件目录的打散 ( Hash目录是一种优化文件存储性能的方法) 需要的jar包: commons-fileupload-1.2.2.jar、commons-io-2.1.jar、dom4j-1.6.1.jar和jaxen-1.1-beta-6.jar 先写index.jsp %@ page language= "java" import= "java.util.*" pageE
SwipeRefreshLayout是Android官方的下拉刷新控件,使用简单,界面美观,不熟悉的朋友可以随便搜索了解一下,这里就不废话了,直接进入正题。 这种下拉刷新控件的原理不难,基本就是监听手指的运动,获取手指的坐标,通过计算判断出是哪种操作,然后就是回调相应的接口了。SwipeRefreshLayout是继承自ViewGroup的,根据Android的事件分发机制,触摸事件应该是先传递到ViewGroup,根据onInterceptTouchEvent的返回值决定是否拦截事件的,那么就onInt

Android 初识Retrofit - 2016-07-25 19:07:08

什么是 Retrofit ? Retrofit 是一套 RESTful 架构的 Android(Java) 客户端实现,基于注解,提供 JSON to POJO(Plain Ordinary Java Object ,简单 Java 对象),POJO to JSON,网络请求(POST,GET, PUT,DELETE 等)封装。 配置环境 在build.gradle中添加 ... .. //编译RxJava compile 'io.reactivex:rxjava:1.1.6' //编译RxAndroid
 Android基础知识(简单实例计算器) 在做这个计算器的时候,我认为主要分为两部分:界面设计,功能实现。 (效果图) 界面设计: 其实界面设计和功能实现是相互联系在一起的,我界面怎么去设计,功能就要去怎么实现。 1、 控件: 界面有19个按钮,数字1-9和小数点、加减乘除、清空回退、百分比、等号,还有一个显示内容的文本框。实现起来十分简单只要拖动到xml中即可。 2、 布局设计: 刚拖进来的控件都摆放得比较凌乱,需要对其调整,本案例中,我会使用LinearLayout对界面进行布局。 3、 样式: 有

Android之广播与服务<一> - 2016-07-25 19:07:17

转发请注明出处: http://blog.csdn.net/qq_28055429/article/details/52014058 前言:作为四大组件成员--广播和服务,虽然在用户使用时它们通常是隐身的,但是好多地方都有它们的身影,如:发送短信,状态栏通知,夜间模式,后台音乐播放等等.... One  ----------- 广播: 一,基本知识: (1)名字: BroadcastReceiver (2)作用: 用于监听系统全局的广播消息,以便实现系统中不同组件之间的通信 (3)经常用途 :飞行模式,后
1、图像坐标系 如图2.1所示,以图像左上角为原点建立以像素为单位的直接坐标系u-v。像素的横坐标u与纵坐标v分别是在其图像数组中所在的列数与所在行数。(在OpenCV中u对应 x,v对应y) 由于(u,v)只代表像素的列数与行数,而像素在图像中的位置并没有用物理单位表示出来,所以,我们还要建立以物理单位(如毫米)表示的图像坐标系x-y。将相机光轴 与图像平面的交点(一般位于图像平面的中心处,也称为图像的主点(principal point)定义为该坐标系的原点O1,且x轴与u轴平行,y轴与v轴平行,假
ART世界探险(6) - 流程控制指令 分支结构 Java分支结构 我们先来个最简单的,比较大小吧。 public static long bigger ( long a, long b){ if (a=b){ return a; } else { return b; } } public static int less ( int a, int b){ if (a=b){ return a; } else { return b; } } 看看Java字节码是个什么样子: public static lo
Handler机制算是我入门源码的第一节。看得比较仔细。体会较多。mark一下。 顺序:先科普一下Handler基本功,然后再细讲下源码 一、Handler目的: 目的:Handler机制来处理了子线程去更新UI线程控件问题。 二、handler,messagequeue,looper,message关系图: 其实各种书籍上都有这么一张图。但是主要是学习源码,所以还是自己手画一张“流程图”。 三、handler知识点总结: ( 若以下总结都能理解,那么可以不再看本文后续源码分析; ) 1)handler、

ART世界探险(5) - 计算指令 - 2016-07-25 18:07:28

ART世界探险(5) - 计算指令 整数运算 Java的整型运算 我们先看看JVM是如何处理这些基本整数运算的吧。 public static long add ( long a, long b){ return a+b; } public static long sub ( long a, long b){ return a-b; } public static long mul ( long a, long b){ return a*b; } public static long div ( long
大部分的软件, 但凡包含登录注册的, 基本都会有选择头像功能, 而其中做的比较有逼格的, 一般会有一个选择框可以裁剪照片。 本文所需要实现的就是这样一种有 逼格 的效果: 右上角加了个图片框,按下确定可以裁剪正方形区域里的图片并显示在右上角。 实现思路: 1:首先需要自定义一个ZoomImageView来显示我们需要的图片,这个View需要让图片能够以合适的位置展现在当前布局的图片展示区域内(合适的位置值的是:如果图片长度大于屏幕,则压缩图片长度至屏幕宽度,高度等比压缩并居中显示,如果图片高度大于屏幕,则