一.今天来给大家分享下一种取巧的方式浸式通知栏,先实现布局图片从通知栏底部开始展示,如图:
1.首先在当前的activity的布局中最外层LinearLayout或RelativeLayout中设置android:fitsSystemWindows="false",
fitsSystemWindows设为false表示布局从通知栏底部开始画;如果为true,表示从通知栏下面开始画;
2.通过上面的方法后呢,但是通知栏不是透明的,通知栏会遮挡住上面的图片,此时要实现一个方法让通知栏保持透明,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//使通知栏透明
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
3.现在通知栏不会遮挡住图片了,但是此时又有个问题了,返回箭头有一半是在通知栏的下面,一般正常手机的通知栏高度是20dp,也可以用另一种通过代码获得通知栏高度,
fun getStateBarHeight(context: Context): Int {
var result = 0
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = context.resources.getDimensionPixelSize(resourceId)
}
return result
}
现在可以在activity初始化返回箭头后,给返回箭头加上一个marginTop,
home_back_linear = findViewById(R.id.home_back_linear)
var backParams = home_back_linear?.layoutParams
var params1 = FrameLayout.LayoutParams(backParams)
params1.topMargin = titleHeight
home_back_linear?.layoutParams = params1
这样箭头放在通知栏之下正常位置了。
二.现在来通知栏和标题栏的背景颜色值根据scrollview滚动的位置实现颜色渐变,如图:
上面有个关键的组件View,因为布局是从屏幕的最顶端开始画起的,所以设置一个view来当做通知栏的View,主要是用来控制状态的背景颜色,然后在代码中初始化status_bar_black_alpha时设置高度:
status_bar_black_alpha = findViewById(R.id.status_bar_black_alpha) as View
var statusParams = status_bar_black_alpha?.layoutParams
statusParams?.height = titleHeight
status_bar_black_alpha?.layoutParams = statusParams
上面还有个head_linear要设置跟返回箭头一样的marginTop高度:
head_linear = findViewById(R.id.head_linear)
var headParams = head_linear?.layoutParams
var params = FrameLayout.LayoutParams(headParams)
params.topMargin = titleHeight
head_linear?.layoutParams = params
然后现在来看看通知栏的颜色背景值根据scrollview滚动渐变:
先看实现自定义的scrollview,这里有个下拉图片放大的功能,模仿以前的QQ qq空间的功能:
package com.ctrip.ibu.ddt.view
import android.animation.ObjectAnimator
import android.content.Context
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.ScrollView
import com.ctrip.ibu.ddt.utils.AndroidUtil
/**
* 初始化获取高度值,并记录
* @param context
* @param attrs
* jgdeng 2018/06/17
*/
class PullToZoomScrollView(context: Context, attrs: AttributeSet) : ScrollView(context, attrs) {
private var isonce: Boolean = false//加载该View的布局时是否是第一次加载,是第一次就让其实现OnMeasure里的代码
private var mParentView: LinearLayout? = null//布局的父布局,ScrollView内部只能有一个根ViewGroup,就是此View
private var mTopView: ViewGroup? = null//这个是带背景的上半部分的View,下半部分的View用不到的
private var mTopView1: ViewGroup? = null
private val mScreenHeight: Int//整个手机屏幕的高度,这是为了初始化该View时设置mTopView用的
private val mTopViewHeight: Int//这个就是mTopView的高度
private val mTopViewWidth: Int
private var mCurrentOffset = 0//当前右侧滚条顶点的偏移量。ScrollView右侧是有滚动条的,当下拉时,
//滚动条向上滑,当向下滑动时,滚动条向下滑动。
private var oa: ObjectAnimator? = null//这个是对象动画,这个在本View里很简单,也很独立,就在这里申明一下,后面有两个方法
private var startY = 0f//向下拉动要放大,手指向下滑时,点击的第一个点的Y坐标
private var isBig: Boolean = false//是否正在向下拉放大上半部分View
private var isTouchOne: Boolean = false//是否是一次连续的MOVE,默认为false,
//在MoVe时,如果发现滑动标签位移量为0,则获取此时的Y坐标,作为起始坐标,然后置为true,为了在连续的Move中只获取一次起始坐标
//当Up弹起时,一次触摸移动完成,将isTouchOne置为false
private var distance = 0f//向下滑动到释放的高度差
private var isScrolledToTop: Boolean = true
private var marginBottom: Int
init {
this.overScrollMode = View.OVER_SCROLL_NEVER
val wm = getContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager
val metrics = DisplayMetrics()
wm.defaultDisplay.getMetrics(metrics)
mScreenHeight = metrics.heightPixels
mTopViewWidth = metrics.widthPixels
// mTopViewHeight = mScreenHeight / 2 - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90f, context.resources.displayMetrics).toInt()
mTopViewHeight = AndroidUtil.dp2px(context, 250f)
marginBottom = AndroidUtil.dp2px(context, 70f)
}
/**
* 将记录的值设置到控件上,并只让控件设置一次
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (!isonce) {
mParentView = this.getChildAt(0) as LinearLayout
mTopView = mParentView?.getChildAt(0) as ViewGroup
mTopView1 = mTopView?.getChildAt(0) as ViewGroup
// mTopView?.layoutParams?.height = mTopViewHeight
isonce = true
}
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
val action = ev.action
when (action) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_MOVE -> {
if (mCurrentOffset <= 0) {
if (!isTouchOne) {
startY = ev.y
isTouchOne = true
}
distance = ev.y - startY
if (distance > 0) {
isBig = true
setT((-distance).toInt() / 4)
}
}
}
MotionEvent.ACTION_UP -> {
if (isBig) {
reset()
isBig = false
}
isTouchOne = false
}
}
return super.onTouchEvent(ev)
}
/**
* 对象动画要有的设置方法
* @param t
*/
fun setT(t: Int) {
scrollTo(0, 0)
if (t < 0) {
var params = RelativeLayout.LayoutParams(mTopView1?.layoutParams)
params?.leftMargin = t / 2
params?.rightMargin = t / 2
mTopView1?.layoutParams = params
mTopView?.layoutParams?.height = mTopViewHeight - t
mTopView?.requestFocus()
}
}
/**
* 主要用于释放手指后的回弹效果
*/
private fun reset() {
if (oa != null && oa?.isRunning == true) {
return
}
oa = ObjectAnimator.ofInt(this, "t", (-distance).toInt() / 4, 0)
oa?.duration = 150
oa?.start()
}
/**
* 这个是设置向上滑动时,上半部分View滑动速度让其小于下半部分
* @param l
* @param t
* @param oldl
* @param oldt
*/
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
super.onScrollChanged(l, t, oldl, oldt)
mCurrentOffset = t//右边滑动标签相对于顶端的偏移量
//当手势上滑,则右侧滚动条下滑,下滑的高度小于TopView的高度,则让TopView的上滑速度小于DownView的上滑速度
//DownView的上滑速度是滚动条的速度,也就是滚动的距离是右侧滚动条的距离
//则TopView的速度要小,只需要将右侧滚动条的偏移量也就是t缩小一定倍数就行了。我这里除以2速度减小1倍
if (t <= mTopViewHeight && t >= 0 && !isBig) {
// mTopView?.translationY = (t / 2).toFloat()//使得TopView滑动的速度小于滚轮滚动的速度
}
if (isBig) {
scrollTo(0, 0)
}
}
var onScrolledToTop: (isScrolledToTop: Boolean?, scrollY: Int) -> Unit = { _: Boolean?, _: Int -> }
override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
isScrolledToTop = if (scrollY == 0) true else false
onScrolledToTop(isScrolledToTop, scrollY)
}
}
主要是通过上面的onScrolledToTop()方法来获取滚动的高度的:
var drawable = head_linear?.background?.mutate()
var status_bar = status_bar_black_alpha?.background?.mutate()
scrollView?.onScrolledToTop = { isScrolledToTop, scrollY ->
println("scrollY--------->$scrollY")
val top = banner_layout?.getChildAt(1)?.top
?: 0
if (scrollY > top) {
val topTxt = top + (play_city_name_layout?.height ?: 0)
home_back?.setTextColor(this.resources.getColor(R.color.ddt_black))
if (topTxt >= scrollY) {
var height = scrollY - topTxt
drawable?.alpha = height * 255 / (topTxt - top)
status_bar?.alpha = height * 255 / (topTxt - top)
} else {
drawable?.alpha = 255
status_bar?.alpha = 255
}
if (flagBackground) {
flagBack = false
head_linear?.visibility = View.VISIBLE
status_bar_black_alpha?.visibility = View.VISIBLE
}
} else {
if (!flagBackground) {
flagBack = true
head_linear?.visibility = View.GONE
status_bar_black_alpha?.visibility = View.GONE
home_back?.setTextColor(this.resources.getColor(R.color.white))
}
}
}
主要是上面drawable?.alpha和status_bar?.alpha 来实现背景透明渐变效果。
三.通过以上的activity布局中status_bar_black_alpha,可以变相的随意设置通知栏的颜色背景,可以实现通知栏背景和标题栏背景一样,还有从通知栏顶部到标题栏颜色值的渐变,
从上面的布局只需要设置head_status_linear的背景颜色就ok的。
总结:以上的方法主要是3个关键点android:fitsSystemWindows="false",this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
然后就是替代通知栏View的status_bar_black_alpha。理解了这三个关键点,就非常的简单可以实现了。