Android 上滑显示底部导航,下滑显示标题bar

本文介绍如何使用属性动画实现上滑显示底部导航与下滑显示标题栏的效果。具体包括布局设计、触摸监听实现及平移动画应用等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      本文简单介绍使用属性动画来实现上滑显示底部导航,下滑显示标题bar。先上图看效果,再分析:


可以看出这是个listview有标题和底部,有点像下拉刷新和上拉加载更多。只不过下拉或上拉一定时位置固定拉不动,且只在list的第一个item出现显示时,才平滑动画的让标题或底部显示或隐藏。

实现思路:

    1、整个布局有三个部分构成,上部由一个RelativeLayout放ImageView或TextView.中间部分是个listView,下部是一个TextView.

    2、采用LinearLayout摆放中下部分,让屏幕初始时把上部分移出屏幕外面。中间listview与底部往上移。

    3、采用平移动画,移动动画有两种一种tween动画一种属性动画。而tween动画是假移事件焦点还在原来地方,而属性动画则是3.0支持的为了兼容3.0以下的这里采用nineoldandroids。

    4、通过给listview设置touch listener,监听手掼是向下滑动的(Y比X移动距离大)且滑动距离足够大时,判断是向下反之亦然是向上。event move过程会有多次回调,为了保证在一次dowm 向下滑动时,需要在down 时设置标志,来保证一次down 向下滑动时只调用动画一次 做显示标题动作。

上代码:

  布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/id_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
     >
    <!-- 上部分 -->
    <RelativeLayout 
        android:id="@+id/rl_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#A8A8A8"
        >
        <ImageView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="20dp"
            android:layout_centerVertical="true"
            android:src="@drawable/icon_back"
            />
        <TextView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="标题111"
            android:layout_centerInParent="true"
            android:textColor="#000000"
            />
         <ImageView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_marginRight="20dp"
            android:layout_centerVertical="true"
            android:src="@drawable/icon_search"
            />
    </RelativeLayout>
    
    <ListView
        android:id="@+id/lv_data"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:listSelector="@null"
        android:dividerHeight="1dp"
        android:divider="#a5a5a5"
         />
        
     <!-- 下部分 -->
    <TextView 
        android:id="@+id/tv_bottom"
         android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="我是底部的栏目"
        android:textSize="18sp"
        android:textColor="#ffffff"
        android:background="#a3a3a3"
        android:gravity="center"
        />
    

</LinearLayout>


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

</pre><p></p><pre>
package com.test.projecta;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.TextureView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.nineoldandroids.animation.ObjectAnimator;

public class HideShowTitleActivity extends Activity {

	private RelativeLayout rl_title;
	private ListView lv_data;
	
	private List<String> mListDatas;
	private boolean mIsShowTitle = false;
	private float mTranslateY;
	private boolean mIsfirstVisible = true;
	private TextView tv_bottom;
	private float mBottomHeight;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_newhideshowtitlelist_back);
		
		findView();
		setListener();
		doLogic();
	}
	
	private void findView() {
		rl_title = (RelativeLayout) findViewById(R.id.rl_title);
		lv_data = (ListView) findViewById(R.id.lv_data);
		tv_bottom = (TextView) findViewById(R.id.tv_bottom);
	}
	
	@Override
	public void onWindowFocusChanged(boolean hasFocus) {
		if(hasFocus){
			//获取listview的高度   那么bottom开始的位置是mTranslateY+height值 
			int height = lv_data.getHeight();
			mBottomHeight = mTranslateY + height;
			showHideTitle(false,0);
		}
	}

	private void setListener() {
		lv_data.setOnScrollListener(new OnScrollListener() {
			
			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {
				
			}
			
			@Override
			public void onScroll(AbsListView view, int firstVisibleItem,
					int visibleItemCount, int totalItemCount) {
				//判断当前是否在显示list的第一项数据
				mIsfirstVisible = firstVisibleItem==0;
				//手掼滑动太快时非显示第一项还显示标题时,隐藏掉标题
				if(mIsShowTitle && !mIsfirstVisible){
					showHideTitle(false,500);
				}
			}
		});
		lv_data.setOnTouchListener(new OnTouchListener() {
			private float lastX;
			private float lastY;
			boolean isChange = false;
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					lastX = event.getX();
					lastY = event.getY();
					isChange = false;
					return false;
				case MotionEvent.ACTION_MOVE:
					float x = event.getX();
					float y = event.getY();
					float xGapDistance = Math.abs(x-lastX);
					float yGapDistance = Math.abs(y-lastY);
					boolean isDown = y-lastY>5;
					//没有显示标题时,且是向下的,就显示
					boolean isShow = yGapDistance > 8 && xGapDistance<8 && !mIsShowTitle  && isDown;
					boolean isHide = yGapDistance > 8 && xGapDistance<8 && mIsShowTitle  && !isDown;
					lastX = x;
					lastY = y;
					//一次down,只变化一次,防止一次滑动时抖动下,造成某一个的向下时,y比lastY小
					if(!isChange&&mIsfirstVisible&&isShow){
						// 显示此标题
						showHideTitle(true,500);
						isChange = true;
					}//显示标题时,且是向上的,就隐蔽
					 else if(!isChange&&mIsfirstVisible&&isHide){
						// 隐蔽标题
						 showHideTitle(false,500);
						 isChange = true;
					}
					break;

				default:
					break;
				}
				return false;
			}
			
		});
	}

	private void showHideTitle(boolean isShow,int duration) {
		if(isShow){
			ObjectAnimator.ofFloat(lv_data, "y",0,mTranslateY).setDuration(duration).start();
			ObjectAnimator.ofFloat(rl_title, "y", -mTranslateY,0).setDuration(duration).start();
			ObjectAnimator.ofFloat(tv_bottom, "y", mBottomHeight-mTranslateY,mBottomHeight).setDuration(duration).start();
			
		}else{//隐藏时,把标题隐藏了,底部出来了
			ObjectAnimator.ofFloat(lv_data, "y", mTranslateY,0).setDuration(duration).start();
			ObjectAnimator.ofFloat(rl_title, "y", 0f,-mTranslateY).setDuration(duration).start();
			ObjectAnimator.ofFloat(tv_bottom, "y", mBottomHeight,mBottomHeight-mTranslateY).setDuration(duration).start();
		}
		mIsShowTitle = isShow;
	}
	
	private void doLogic() {
		mListDatas = new ArrayList<String>();
		for(int i=0;i<50;i++){
			mListDatas.add("数据"+i);
		}
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListDatas);
		lv_data.setAdapter(adapter);
		//将标题栏高度50dp转成显示的高度
		mTranslateY = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics());
	}
	
}

OK。。。。。。其实,这个还有更简单的实现,直接将整个布局ll_content进行向上或向下移动即可。够简单的吧...

<think>我们参考了上述引用资料,特别是引用[1]中提到的`setDecorFitsSystemWindows`方法,引用[2]中提到的`layoutNavigationBar`和`layoutStatusBar`方法,引用[3]中提到的`hideNavigationBar`方法,引用[4]中提到的自动隐藏方法,以及引用[5]中提到的`setSystemUiVisibility`方法。 目标:开发一个系统接口,供上层应用调用以隐藏状态栏和导航栏。 步骤: 1. 在系统服务中增加一个新的服务(例如`WindowManagerService`扩展)或使用现有服务(如`StatusBarManagerService`)来提供隐藏/显示状态栏和导航栏的方法。 2. 定义AIDL接口,以便应用可以跨进程调用。 3. 实现接口方法,调用系统内部隐藏状态栏和导航栏的功能。 4. 在系统服务中实现具体的隐藏逻辑,可能需要修改`PhoneWindowManager`、`StatusBarManagerService`和`NavigationBarController`等。 5. 为应用提供权限控制,确保只有授权应用才能调用。 详细步骤: 步骤1:定义AIDL接口 在`frameworks/base/core/java/android/view/`下创建`IWindowManagerEx.aidl`(或扩展现有接口): ```aidl package android.view; interface IWindowManagerEx { // 隐藏状态栏和导航栏 void hideSystemBars(); // 显示状态栏和导航栏 void showSystemBars(); } ``` 步骤2:在`WindowManagerService`中实现接口 修改`frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java`,添加接口实现: ```java @Override public void hideSystemBars() { // 检查调用者权限 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.HIDE_SYSTEM_BARS, "hideSystemBars"); synchronized (mGlobalLock) { // 调用PhoneWindowManager的方法 mPolicy.hideSystemBars(); } } @Override public void showSystemBars() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.HIDE_SYSTEM_BARS, "showSystemBars"); synchronized (mGlobalLock) { mPolicy.showSystemBars(); } } ``` 步骤3:修改`PhoneWindowManager`(位于`frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java`)实现隐藏显示逻辑: 在`PhoneWindowManager`中添加方法: ```java public void hideSystemBars() { // 设置系统UI可见性标志,隐藏状态栏和导航栏 // 使用View.SYSTEM_UI_FLAG_FULLSCREEN和View.SYSTEM_UI_FLAG_HIDE_NAVIGATION等 // 这里我们通过改变全局设置,然后通知状态栏和导航隐藏 // 实际上,我们可以通过修改系统UI的可见性标志,然后通知SystemUI更新 // 例如,我们可以调用StatusBarManagerService和NavigationBarController的方法 // 方式1:通过SystemUI的控制器(需要跨进程调用SystemUI,或者通过WindowManagerService内部方法) // 方式2:直接设置全局标志,然后让SystemUI监听并更新 // 这里我们采用方式2:设置全局标志,然后通知更新 // 我们可以使用Settings.Global.putInt()来设置一个标志,然后SystemUI监听这个标志变化并隐藏 // 但更直接的方式是调用SystemUI中提供的服务方法 // 参考引用[3]中,NavigationBarController有hideNavigationBar方法,我们可以调用它 // 同样,对于状态栏,StatusBarManagerService也有相应的方法 // 由于PhoneWindowManager在系统进程中,可以直接调用SystemUI服务 try { IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); if (statusBarService != null) { // 隐藏状态栏(通知栏) statusBarService.collapsePanels(); // 设置状态栏隐藏(完全隐藏状态栏) statusBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_FULLSCREEN, 0, 0, IStatusBarService.USER_ALL); } // 隐藏导航栏,通过NavigationBarController // 但是NavigationBarController在SystemUI进程中,我们需要通过SystemUI的接口调用 // 或者通过WindowManagerService的mWindowManagerFuncs(需要扩展接口) // 这里我们使用另一种方式:通过设置系统UI可见性标志,然后通知所有窗口更新 // 但是,为了直接控制导航栏,我们可以调用NavigationBarController的hideNavigationBar方法 // 由于在PhoneWindowManager中无法直接获取到NavigationBarController,因此我们需要通过其他方式 // 替代方案:使用WindowManagerPolicy中控制导航栏的方法 // 在PhoneWindowManager中,我们可以通过mWindowManagerFuncs来与WindowManagerService通信 // 但这里我们采用修改系统UI可见性的方式 // 设置全局标志,让SystemUI的NavigationBarController自己隐藏 // 或者,我们可以在PhoneWindowManager中直接控制导航栏的显示状态 // 例如,设置一个成员变量,然后在布局时应用 // 由于时间关系,我们采用直接设置系统UI可见性标志的方式,这种方式会影响到所有窗口 // 但是,我们只希望隐藏状态栏和导航栏,而不是整个系统UI // 因此,我们可以通过调用StatusBarManager的disable方法(引用[5]) // 使用disable方法禁用状态栏(包括下拉)和导航栏 int disableFlags = StatusBarManager.DISABLE_EXPAND | StatusBarManager.DISABLE_NAVIGATION; statusBarService.disable(disableFlags, mToken, mContext.getPackageName(), UserHandle.USER_CURRENT); } catch (RemoteException e) { // 忽略 } } public void showSystemBars() { try { IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); if (statusBarService != null) { // 清除禁用标志 statusBarService.disable(0, mToken, mContext.getPackageName(), UserHandle.USER_CURRENT); } } catch (RemoteException e) { // 忽略 } } ``` 注意:上述实现中,我们使用了`StatusBarService`的`disable`方法来隐藏状态栏和导航栏。但是,这种方法会禁用状态栏和导航栏的交互,同时隐藏它们。另外,我们还需要一个token来标识调用者,以便在调用者退出时恢复。 步骤4:在`StatusBarManagerService`中实现disable方法(实际上已经存在,我们只需要确保其功能正确): 引用[5]中已经说明,`StatusBarManagerService`的`disable`方法可以控制状态栏和导航栏的禁用(隐藏)。我们只需要确保调用正确。 步骤5:权限控制 在`AndroidManifest.xml`中声明权限: ```xml <permission android:name="android.permission.HIDE_SYSTEM_BARS" android:protectionLevel="signature" /> ``` 步骤6:为应用提供调用方式 我们可以创建一个`WindowManagerEx`类,提供静态方法: ```java public class WindowManagerEx { public static void hideSystemBars() { IWindowManagerEx wm = IWindowManagerEx.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); if (wm != null) { try { wm.hideSystemBars(); } catch (RemoteException e) { Log.e("WindowManagerEx", "hideSystemBars failed", e); } } } public static void showSystemBars() { // 类似 } } ``` 但是,由于我们修改了系统服务,需要重新编译系统。 注意:上述实现是一个简化的示例,实际开发中需要考虑更多细节,例如: - 隐藏后如何恢复(例如在应用退出时自动恢复) - 多用户支持 - 系统UI的稳定性 另外,引用[4]中提到了一种自动隐藏的方法,我们可以参考其思路,在系统服务中增加一个无操作自动隐藏的功能,但本问题要求的是提供接口供上层调用,因此我们实现了主动调用的接口。 另一种更简单的方式:应用可以通过设置窗口的systemUiVisibility来隐藏状态栏和导航栏,例如: ```java View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE); ``` 但是,这种方式只对当前应用窗口有效,且当用户交互时可能会重新显示。而我们的需求是提供一个系统接口,让应用可以全局隐藏状态栏和导航栏(需要系统权限)。 因此,我们提供的系统接口应该具有全局效果,并且需要系统签名权限。 总结:我们通过扩展`WindowManagerService`,添加了`hideSystemBars`和`showSystemBars`方法,并利用`StatusBarManagerService`的`disable`方法实现了全局隐藏状态栏和导航栏的功能。 注意:由于系统UI的复杂性,实际实现可能需要根据具体系统版本进行调整。 相关问题: 1. 如何实现在应用退出时自动恢复状态栏和导航栏? 2. 如何确保隐藏状态栏和导航栏后不影响其他应用? 3. 除了禁用状态栏和导航栏,还有哪些方法可以隐藏它们?</think>在Android系统中开发隐藏状态栏和导航栏的系统接口,需要修改系统服务和SystemUI组件。以下是具体实现步骤: ### 一、系统服务层实现 1. **在`StatusBarManagerService`中添加接口** ```java // frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java public void hideSystemBars() { enforceStatusBarService(); synchronized (mLock) { // 通知SystemUI隐藏状态栏和导航栏 mBarMap.forEach((displayId, bar) -> { bar.hideSystemBars(); }); } } public void showSystemBars() { enforceStatusBarService(); synchronized (mLock) { // 恢复显示 mBarMap.forEach((displayId, bar) -> { bar.showSystemBars(); }); } ``` 2. **创建AIDL接口供应用调用** ```java // frameworks/base/core/java/android/app/IStatusBarServiceEx.aidl interface IStatusBarServiceEx { void hideSystemBars(); void showSystemBars(); } ``` ### 二、SystemUI组件实现 1. **在`NavigationBarController`添加控制逻辑** [^3] ```java // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java public void hideNavigationBar() { Display[] displays = mDisplayManager.getDisplays(); for (Display display : displays) { removeNavigationBar(display.getDisplayId()); } } public void showNavigationBar() { Display[] displays = mDisplayManager.getDisplays(); for (Display display : displays) { createNavigationBar(display); } } ``` 2. **在`StatusBar`组件添加控制方法** [^5] ```java // frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java public void hideSystemBars() { // 隐藏状态栏 mStatusBarWindowController.setForceHide(true); // 隐藏导航栏 NavigationBarController navController = Dependency.get(NavigationBarController.class); navController.hideNavigationBar(); } public void showSystemBars() { mStatusBarWindowController.setForceHide(false); NavigationBarController navController = Dependency.get(NavigationBarController.class); navController.showNavigationBar(); } ``` ### 三、权限控制 在`AndroidManifest.xml`添加系统权限: ```xml <permission android:name="android.permission.HIDE_SYSTEM_UI" android:protectionLevel="signature|system" /> ``` ### 四、上层应用调用示例 ```java // 获取系统服务 IStatusBarServiceEx statusBarService = IStatusBarServiceEx.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); // 隐藏系统栏 statusBarService.hideSystemBars(); // 恢复显示 statusBarService.showSystemBars(); ``` ### 五、关键实现原理 1. **状态栏控制**:通过`StatusBarWindowController`强制设置`forceHide`标志,阻止状态栏绘制 [^1] 2. **导航栏控制**:通过`NavigationBarController`动态创建/移除导航栏视图 [^3] 3. **窗口管理**:在`PhoneWindowManager`的`layoutNavigationBar()`方法中,根据隐藏状态跳过布局计算 [^2] 4. **跨进程通信**:通过Binder机制实现系统服务与应用进程的通信 ### 注意事项 1. 需要系统签名权限(`signature`保护级别) 2.显示器场景需遍历所有displayId处理 [^3] 3. 隐藏后需处理输入事件重定向 4. 系统重启后状态持久化需结合`Settings.Global`存储状态 > 实现效果:调用后全局隐藏状态栏和导航栏,从屏幕顶部下滑可临时显示状态栏,底部显示导航栏(类似沉浸模式增强版)[^4][^5]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值