編輯:關於Android編程
由於公司的項目基本上都是以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布局文件的代碼如下所示:
獲取狀態欄高度的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;
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 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);
}
}
Android NDK環境搭建與簡單實例
一、NDK與JNI簡介 NDK全稱為native development kit本地語言(C&C++)開發包。而對應的是經常接觸的Android-SDK
Android消息通知欄的實現方法介紹
背景知識:可以用Activity和Service來開始消息通知,兩者的區別在於一個是在前台觸發,一個是後台服務觸發。要使用消息通知,必須要用到兩個類:Notificati
Android事件驅動編程-基於EventBus(一)
Android事件驅動編程-基於EventBus(一) 雖然在Android開發具有某些事件驅動的特性,但它還遠不是純粹的事件驅動架構。這算是好事還是壞事呢
Android實現天氣預報溫度/氣溫折線趨勢圖
??Android實現天氣預報溫度/氣溫折線趨勢圖天氣預報的APP應用中,難免會遇到繪制天氣溫度/氣溫,等關於數據趨勢的折線或者曲線圖,這類關於氣溫/溫度的折線圖,一般會