improved user fragment layout

This commit is contained in:
Mariotaku Lee 2017-10-26 20:25:44 +08:00
parent a3547e82a0
commit 0ec9096a9d
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
11 changed files with 327 additions and 175 deletions

View File

@ -345,9 +345,10 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher,
this[EXTRA_REQUEST_TOKEN_SECRET] = requestToken.oauthTokenSecret this[EXTRA_REQUEST_TOKEN_SECRET] = requestToken.oauthTokenSecret
}) })
activity.startActivityForResult(intent, REQUEST_BROWSER_TWITTER_SIGN_IN) activity.startActivityForResult(intent, REQUEST_BROWSER_TWITTER_SIGN_IN)
}.failUi { }.failUi { exception ->
val activity = weakThis.get() ?: return@failUi val activity = weakThis.get() ?: return@failUi
// TODO show error message Toast.makeText(activity, exception.getErrorMessage(activity), Toast.LENGTH_LONG).show()
DebugLog.w(tr = exception)
}.alwaysUi { }.alwaysUi {
executeAfterFragmentResumed { executeAfterFragmentResumed {
it.supportFragmentManager.dismissDialogFragment("get_request_token") it.supportFragmentManager.dismissDialogFragment("get_request_token")

View File

@ -20,10 +20,10 @@
package org.mariotaku.twidere.extension.model package org.mariotaku.twidere.extension.model
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.graphics.drawable.Drawable
import android.text.Spannable import android.text.Spannable
import android.text.Spanned import android.text.Spanned
import android.text.style.URLSpan import android.text.style.URLSpan
import android.widget.TextView
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import org.mariotaku.twidere.model.CustomEmoji import org.mariotaku.twidere.model.CustomEmoji
import org.mariotaku.twidere.model.SpanItem import org.mariotaku.twidere.model.SpanItem
@ -36,7 +36,7 @@ val SpanItem.length: Int get() = end - start
@SuppressLint("SwitchIntDef") @SuppressLint("SwitchIntDef")
fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoji>?, fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoji>?,
requestManager: RequestManager, callback: Drawable.Callback) { requestManager: RequestManager, textView: TextView) {
forEach { span -> forEach { span ->
when (span.type) { when (span.type) {
SpanItem.SpanType.HIDE -> { SpanItem.SpanType.HIDE -> {
@ -54,7 +54,7 @@ fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoj
SpanItem.SpanType.EMOJI -> { SpanItem.SpanType.EMOJI -> {
val shortCode = span.link ?: return@forEach val shortCode = span.link ?: return@forEach
val emoji = emojis?.get(shortCode) ?: return@forEach val emoji = emojis?.get(shortCode) ?: return@forEach
spannable.setSpan(CustomEmojiSpan(emoji.url, requestManager, callback), span.start, spannable.setSpan(CustomEmojiSpan(emoji.url, requestManager, textView), span.start,
span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
} }
else -> { else -> {

View File

@ -22,24 +22,19 @@ package org.mariotaku.twidere.fragment
import android.accounts.AccountManager import android.accounts.AccountManager
import android.animation.ArgbEvaluator import android.animation.ArgbEvaluator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Outline
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.nfc.NdefMessage import android.nfc.NdefMessage
import android.nfc.NdefRecord import android.nfc.NdefRecord
import android.nfc.NfcAdapter.CreateNdefMessageCallback import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.support.annotation.ColorRes import android.support.annotation.ColorRes
@ -118,8 +113,8 @@ import org.mariotaku.twidere.fragment.timeline.AbsTimelineFragment
import org.mariotaku.twidere.fragment.timeline.FavoritesTimelineFragment import org.mariotaku.twidere.fragment.timeline.FavoritesTimelineFragment
import org.mariotaku.twidere.fragment.timeline.UserMediaTimelineFragment import org.mariotaku.twidere.fragment.timeline.UserMediaTimelineFragment
import org.mariotaku.twidere.fragment.timeline.UserTimelineFragment import org.mariotaku.twidere.fragment.timeline.UserTimelineFragment
import org.mariotaku.twidere.graphic.ActionBarColorDrawable
import org.mariotaku.twidere.graphic.ActionIconDrawable import org.mariotaku.twidere.graphic.ActionIconDrawable
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
import org.mariotaku.twidere.loader.ParcelableUserLoader import org.mariotaku.twidere.loader.ParcelableUserLoader
import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.event.FriendshipTaskEvent import org.mariotaku.twidere.model.event.FriendshipTaskEvent
@ -637,7 +632,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
profileBanner.onSizeChangedListener = this profileBanner.onSizeChangedListener = this
profileBannerSpace.setOnTouchListener(this) profileBannerSpace.setOnTouchListener(this)
profileNameBackground.setBackgroundColor(cardBackgroundColor) profileHeaderBackground.setBackgroundColor(cardBackgroundColor)
toolbarTabs.setBackgroundColor(cardBackgroundColor) toolbarTabs.setBackgroundColor(cardBackgroundColor)
actionBarBackground = ActionBarDrawable(ResourcesCompat.getDrawable(activity.resources, actionBarBackground = ActionBarDrawable(ResourcesCompat.getDrawable(activity.resources,
@ -1301,8 +1296,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
location.setLinkTextColor(optimalAccentColor) location.setLinkTextColor(optimalAccentColor)
url.setLinkTextColor(optimalAccentColor) url.setLinkTextColor(optimalAccentColor)
profileBanner.setBackgroundColor(color) profileBanner.setBackgroundColor(color)
toolbarTabs.setBackgroundColor(primaryColor)
} }
private fun setupBaseActionBar() { private fun setupBaseActionBar() {
@ -1370,11 +1363,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
private fun updateScrollOffset(offset: Int) { private fun updateScrollOffset(offset: Int) {
val spaceHeight = profileBannerSpace.height val spaceHeight = profileBannerSpace.height
val factor = (if (spaceHeight == 0) 0f else offset / spaceHeight.toFloat()).coerceIn(0f, 1f) val factor = (if (spaceHeight == 0) 0f else offset / spaceHeight.toFloat()).coerceIn(0f, 1f)
profileBannerContainer.translationY = (-offset).toFloat()
profileBanner.translationY = (offset / 2).toFloat()
if (profileBirthdayStub == null) {
profileBirthdayBanner.translationY = (offset / 2).toFloat()
}
val activity = activity as BaseActivity val activity = activity as BaseActivity
@ -1386,7 +1374,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
val stackedTabColor = primaryColor val stackedTabColor = primaryColor
val profileContentHeight = profileNameBackground.height.toFloat() val profileContentHeight = profileHeaderBackground.height.toFloat()
val tabOutlineAlphaFactor: Float val tabOutlineAlphaFactor: Float
if (offset - spaceHeight > 0) { if (offset - spaceHeight > 0) {
tabOutlineAlphaFactor = 1f - ((offset - spaceHeight) / profileContentHeight).coerceIn(0f, 1f) tabOutlineAlphaFactor = 1f - ((offset - spaceHeight) / profileContentHeight).coerceIn(0f, 1f)
@ -1514,70 +1502,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
} }
} }
private class ActionBarDrawable(shadow: Drawable) : LayerDrawable(arrayOf(shadow, ActionBarColorDrawable.create(true))) {
private val shadowDrawable = getDrawable(0)
private val colorDrawable = getDrawable(1) as ColorDrawable
private var alphaValue: Int = 0
var factor: Float = 0f
set(value) {
field = value
updateValue()
}
var color: Int = 0
set(value) {
field = value
colorDrawable.color = value
updateValue()
}
var outlineAlphaFactor: Float = 0f
set(value) {
field = value
updateValue()
}
init {
alpha = 0xFF
updateValue()
}
override fun setAlpha(alpha: Int) {
alphaValue = alpha
updateValue()
}
override fun getAlpha(): Int {
return alphaValue
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun getOutline(outline: Outline) {
colorDrawable.getOutline(outline)
outline.alpha = factor * outlineAlphaFactor * 0.99f
}
override fun getIntrinsicWidth(): Int {
return colorDrawable.intrinsicWidth
}
override fun getIntrinsicHeight(): Int {
return colorDrawable.intrinsicHeight
}
private fun updateValue() {
val shadowAlpha = Math.round(alpha * (1 - factor).coerceIn(0f, 1f))
shadowDrawable.alpha = shadowAlpha
val hasColor = color != 0
val colorAlpha = if (hasColor) Math.round(alpha * factor.coerceIn(0f, 1f)) else 0
colorDrawable.alpha = colorAlpha
invalidateSelf()
}
}
internal class UserRelationshipLoader( internal class UserRelationshipLoader(
context: Context, context: Context,

View File

@ -0,0 +1,92 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.graphic.drawable.userprofile
import android.annotation.TargetApi
import android.graphics.Outline
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import org.mariotaku.twidere.graphic.ActionBarColorDrawable
internal class ActionBarDrawable(shadow: Drawable) : LayerDrawable(arrayOf(shadow, ActionBarColorDrawable.create(true))) {
private val shadowDrawable = getDrawable(0)
private val colorDrawable = getDrawable(1) as ColorDrawable
private var alphaValue: Int = 0
var factor: Float = 0f
set(value) {
field = value
updateValue()
}
var color: Int = 0
set(value) {
field = value
colorDrawable.color = value
updateValue()
}
var outlineAlphaFactor: Float = 0f
set(value) {
field = value
updateValue()
}
init {
alpha = 0xFF
updateValue()
}
override fun setAlpha(alpha: Int) {
alphaValue = alpha
updateValue()
}
override fun getAlpha(): Int {
return alphaValue
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun getOutline(outline: Outline) {
colorDrawable.getOutline(outline)
outline.alpha = factor * outlineAlphaFactor * 0.99f
}
override fun getIntrinsicWidth(): Int {
return colorDrawable.intrinsicWidth
}
override fun getIntrinsicHeight(): Int {
return colorDrawable.intrinsicHeight
}
private fun updateValue() {
val shadowAlpha = Math.round(alpha * (1 - factor).coerceIn(0f, 1f))
shadowDrawable.alpha = shadowAlpha
val hasColor = color != 0
val colorAlpha = if (hasColor) Math.round(alpha * factor.coerceIn(0f, 1f)) else 0
colorDrawable.alpha = colorAlpha
invalidateSelf()
}
}

View File

@ -19,77 +19,89 @@
package org.mariotaku.twidere.text.style package org.mariotaku.twidere.text.style
import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Rect import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v4.widget.CircularProgressDrawable import android.text.style.ReplacementSpan
import android.text.style.DynamicDrawableSpan
import android.widget.TextView import android.widget.TextView
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.resource.drawable.GlideDrawable
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.BaseTarget import com.bumptech.glide.request.target.BaseTarget
import com.bumptech.glide.request.target.SizeReadyCallback import com.bumptech.glide.request.target.SizeReadyCallback
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.graphic.DrawableWrapper import java.lang.ref.WeakReference
class CustomEmojiSpan( class CustomEmojiSpan(
val uri: String, val uri: String,
requestManager: RequestManager, requestManager: RequestManager,
callback: Drawable.Callback val textView: TextView,
) : DynamicDrawableSpan(DynamicDrawableSpan.ALIGN_BASELINE) { val alignBaseline: Boolean = false
) : ReplacementSpan() {
private val textSize = (callback as TextView).textSize.toInt() private val textSize = textView.textSize.toInt()
private val emojiDrawable = EmojiDrawable(textSize) private val target = GlideTarget(textSize)
private val emojiDirtyBounds = Rect()
private val target = GlideTarget()
init { init {
emojiDrawable.callback = callback
requestManager.load(uri) requestManager.load(uri)
.placeholder(CircularProgressDrawable((callback as TextView).context)) .asBitmap()
.placeholder(R.mipmap.ic_emoji_loading)
.error(R.mipmap.ic_emoji_error) .error(R.mipmap.ic_emoji_error)
.fitCenter() .fitCenter()
.dontAnimate()
.into(target) .into(target)
} }
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int,
fm: Paint.FontMetricsInt?): Int {
return textSize
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
y: Int, bottom: Int, paint: Paint) { y: Int, bottom: Int, paint: Paint) {
val b = target.drawable ?: return
canvas.save()
var transY = bottom - emojiDrawable.bounds.bottom var transY = bottom - b.bounds.bottom
if (verticalAlignment == ALIGN_BASELINE) { if (alignBaseline) {
transY -= paint.fontMetricsInt.descent transY -= paint.fontMetricsInt.descent
} }
emojiDrawable.copyBounds(emojiDirtyBounds)
emojiDirtyBounds.offsetTo(x.toInt(), transY) canvas.translate(x, transY.toFloat())
super.draw(canvas, text, start, end, x, top, y, bottom, paint) b.setBounds(0, 0, textSize, textSize)
b.draw(canvas)
canvas.restore()
} }
override fun getDrawable(): Drawable = emojiDrawable private inner class GlideTarget(
val textSize: Int
) : BaseTarget<Bitmap>() {
fun verify(who: Drawable): Boolean = who === emojiDrawable var drawable: Drawable?
get() = drawableRef?.get()
private set(value) {
drawableRef = if (value != null) WeakReference(value) else null
textView.invalidate()
}
private inner class GlideTarget : BaseTarget<GlideDrawable>() { private var drawableRef: WeakReference<Drawable>? = null
override fun onResourceReady(resource: GlideDrawable, glideAnimation: GlideAnimation<in GlideDrawable>) { override fun onResourceReady(resource: Bitmap, glideAnimation: GlideAnimation<in Bitmap>) {
resource.setLoopCount(GlideDrawable.LOOP_FOREVER) drawable = BitmapDrawable(textView.resources, resource)
emojiDrawable.setDrawable(resource)
} }
override fun onLoadCleared(placeholder: Drawable?) { override fun onLoadCleared(placeholder: Drawable?) {
emojiDrawable.setDrawable(placeholder) drawable = placeholder
} }
override fun onLoadStarted(placeholder: Drawable?) { override fun onLoadStarted(placeholder: Drawable?) {
emojiDrawable.setDrawable(placeholder) drawable = placeholder
} }
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
emojiDrawable.setDrawable(errorDrawable) drawable = errorDrawable
} }
override fun getSize(cb: SizeReadyCallback) { override fun getSize(cb: SizeReadyCallback) {
@ -98,20 +110,4 @@ class CustomEmojiSpan(
} }
private inner class EmojiDrawable(val textSize: Int) : DrawableWrapper() {
override fun getDirtyBounds(): Rect {
return emojiDirtyBounds
}
fun setDrawable(drawable: Drawable?) {
wrapped = drawable
drawable?.setBounds(0, 0, textSize, textSize)
setBounds(0, 0, textSize, textSize)
if (drawable is Animatable) {
drawable.start()
}
}
}
} }

View File

@ -21,9 +21,7 @@ package org.mariotaku.twidere.view
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Spannable import android.text.Spannable
import android.text.Spanned
import android.text.method.BaseMovementMethod import android.text.method.BaseMovementMethod
import android.text.method.MovementMethod import android.text.method.MovementMethod
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
@ -32,7 +30,6 @@ import android.view.MotionEvent
import android.widget.TextView import android.widget.TextView
import org.mariotaku.chameleon.view.ChameleonTextView import org.mariotaku.chameleon.view.ChameleonTextView
import org.mariotaku.twidere.extension.setupEmojiFactory import org.mariotaku.twidere.extension.setupEmojiFactory
import org.mariotaku.twidere.text.style.CustomEmojiSpan
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
/** /**
@ -90,16 +87,6 @@ class TimelineContentTextView(
} }
} }
override fun verifyDrawable(who: Drawable): Boolean {
val result = super.verifyDrawable(who)
if (result) return true
val spanned = text as? Spanned ?: return false
val spans = spanned.getSpans(0, length(), CustomEmojiSpan::class.java)
return spans.any {
it.verify(who)
}
}
internal class InternalMovementMethod : BaseMovementMethod() { internal class InternalMovementMethod : BaseMovementMethod() {
private var targetSpan: WeakReference<ClickableSpan?>? = null private var targetSpan: WeakReference<ClickableSpan?>? = null

View File

@ -19,20 +19,40 @@
package org.mariotaku.twidere.view.behavior.userprofile package org.mariotaku.twidere.view.behavior.userprofile
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.support.design.widget.AccessorHeaderBehavior import android.support.design.widget.AccessorHeaderBehavior
import android.support.design.widget.CoordinatorLayout import android.support.design.widget.CoordinatorLayout
import android.support.v4.math.MathUtils import android.support.graphics.drawable.ArgbEvaluator
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import kotlinx.android.synthetic.main.fragment_user.view.*
import kotlinx.android.synthetic.main.header_user.view.*
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.themeBackgroundAlphaKey
import org.mariotaku.twidere.constant.themeBackgroundOptionKey
import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.util.dagger.DependencyHolder
internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderBehavior<View>(context, attrs) { internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderBehavior<View>(context, attrs) {
var offsetDelta: Int = 0 var offsetDelta: Int = 0
private set private set
private val cardBackgroundColor: Int
private var tabItemIsDark: Int = 0
init {
val preferences = DependencyHolder.get(context).preferences
cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
preferences[themeBackgroundOptionKey], preferences[themeBackgroundAlphaKey])
}
override fun onMeasureChild(parent: CoordinatorLayout, child: View, override fun onMeasureChild(parent: CoordinatorLayout, child: View,
parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int,
heightUsed: Int): Boolean { heightUsed: Int): Boolean {
@ -40,6 +60,12 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
parentHeightMeasureSpec, heightUsed) parentHeightMeasureSpec, heightUsed)
} }
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
val result = super.onLayoutChild(parent, child, layoutDirection)
updateTabColor(parent, child, topAndBottomOffset)
return result
}
override fun layoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int) { override fun layoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int) {
child.layout(0, 0, child.measuredWidth, child.measuredHeight) child.layout(0, 0, child.measuredWidth, child.measuredHeight)
} }
@ -62,38 +88,19 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
val curOffset = topBottomOffsetForScrollingSibling val curOffset = topBottomOffsetForScrollingSibling
var consumed = 0 var consumed = 0
var newOffset = newOffset
if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) { if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
// If we have some scrolling range, and we're currently within the min and max // If we have some scrolling range, and we're currently within the min and max
// offsets, calculate a new offset // offsets, calculate a new offset
newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset) val clampedOffset = newOffset.coerceIn(minOffset, maxOffset)
if (curOffset != newOffset) { if (curOffset != clampedOffset) {
val interpolatedOffset = if (header.hasChildWithInterpolator)
interpolateOffset(header, newOffset)
else
newOffset
val offsetChanged = setTopAndBottomOffset(interpolatedOffset)
topAndBottomOffset = clampedOffset
updateTabColor(parent, header, clampedOffset)
// Update how much dy we have consumed // Update how much dy we have consumed
consumed = curOffset - newOffset consumed = curOffset - clampedOffset
// Update the stored sibling offset // Update the stored sibling offset
offsetDelta = newOffset - interpolatedOffset offsetDelta = clampedOffset - clampedOffset
if (!offsetChanged && header.hasChildWithInterpolator) {
// If the offset hasn't changed and we're using an interpolated scroll
// then we need to keep any dependent views updated. CoL will do this for
// us when we move, but we need to do it manually when we don't (as an
// interpolated scroll may finish early).
parent.dispatchDependentViewsChanged(header)
}
// Dispatch the updates to any listeners
// header.dispatchOffsetUpdates(topAndBottomOffset)
// Update the AppBarLayout's drawable state (for any elevation changes)
// updateAppBarLayoutDrawableState(parent, header, newOffset,
// if (newOffset < curOffset) -1 else 1, false)
} }
} else { } else {
// Reset the offset delta // Reset the offset delta
@ -103,13 +110,42 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
return consumed return consumed
} }
override fun canDragView(view: View) = true @SuppressLint("RestrictedApi")
private fun updateTabColor(parent: CoordinatorLayout, header: View, offset: Int) {
val actionBarBackground = parent.toolbar.background as? ActionBarDrawable ?: return
val profileHeaderBackground = parent.profileHeaderBackground
private fun interpolateOffset(header: View, offset: Int): Int { val toolbarBottom = parent.toolbar.bottom
return offset val headerBackgroundOffset = offset + profileHeaderBackground.top
val factor = ((toolbarBottom - headerBackgroundOffset) / profileHeaderBackground.height.toFloat()).coerceIn(0f, 1f)
val toolbarTabs = header.toolbarTabs
val colorPrimary = actionBarBackground.color
val currentTabColor = ArgbEvaluator.getInstance().evaluate(factor,
cardBackgroundColor, colorPrimary) as Int
toolbarTabs.setBackgroundColor(currentTabColor)
val tabItemIsDark = if (ThemeUtils.isLightColor(currentTabColor)) 1 else -1
if (this.tabItemIsDark != tabItemIsDark) {
val context = parent.context
val activity = ChameleonUtils.getActivity(context)
val tabContrastColor = ThemeUtils.getColorDependent(currentTabColor)
toolbarTabs.setIconColor(tabContrastColor)
toolbarTabs.setLabelColor(tabContrastColor)
val theme = Chameleon.getOverrideTheme(context, activity)
if (theme.isToolbarColored) {
toolbarTabs.setStripColor(tabContrastColor)
} else {
toolbarTabs.setStripColor(ThemeUtils.getOptimalAccentColor(colorPrimary, tabContrastColor))
}
toolbarTabs.updateAppearance()
}
this.tabItemIsDark = tabItemIsDark
} }
private val View.hasChildWithInterpolator: Boolean override fun canDragView(view: View) = true
get() = false
} }

View File

@ -0,0 +1,75 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.view.behavior.userprofile
import android.annotation.SuppressLint
import android.content.Context
import android.support.design.widget.CoordinatorLayout
import android.support.design.widget.lastWindowInsetsCompat
import android.support.graphics.drawable.ArgbEvaluator
import android.util.AttributeSet
import android.view.View
import android.view.Window
import kotlinx.android.synthetic.main.fragment_user.view.*
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.twidere.R
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.util.support.WindowSupport
internal class StatusBarBehavior(context: Context, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<View>(context, attrs) {
private val window: Window = ChameleonUtils.getActivity(context)!!.window
private var lightStatusBar: Int = 0
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
return dependency.id == R.id.profileHeader
}
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
val lastInsets = parent.lastWindowInsetsCompat ?: return true
val height = lastInsets.systemWindowInsetTop
child.layout(0, 0, child.measuredWidth, height)
return true
}
@SuppressLint("RestrictedApi")
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
if (child.height == 0) return true
val actionBarBackground = parent.toolbar.background as? ActionBarDrawable ?: return true
val bannerContainer = parent.profileBannerContainer
val bannerBottom = dependency.top + bannerContainer.height
val currentOffset = bannerBottom - child.bottom
val maxOffset = (bannerContainer.height - child.bottom).toFloat()
val factor = (1 - currentOffset / maxOffset).coerceIn(0f, 1f)
val primaryColorDark = ChameleonUtils.darkenColor(actionBarBackground.color)
val statusBarColor = ArgbEvaluator.getInstance().evaluate(factor, 0xA0000000.toInt(),
ChameleonUtils.darkenColor(primaryColorDark))
child.setBackgroundColor(statusBarColor as Int)
val lightStatusBar = if (ThemeUtils.isLightColor(statusBarColor)) 1 else -1
if (this.lightStatusBar != lightStatusBar) {
WindowSupport.setLightStatusBar(window, lightStatusBar == 1)
}
this.lightStatusBar = lightStatusBar
return true
}
}

View File

@ -19,11 +19,47 @@
package org.mariotaku.twidere.view.behavior.userprofile package org.mariotaku.twidere.view.behavior.userprofile
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.support.design.widget.CoordinatorLayout import android.support.design.widget.CoordinatorLayout
import android.support.graphics.drawable.ArgbEvaluator
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import kotlinx.android.synthetic.main.fragment_user.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
import org.mariotaku.twidere.util.ThemeUtils
internal class ToolbarBehavior(context: Context?, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<Toolbar>(context, attrs) { internal class ToolbarBehavior(context: Context?, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<Toolbar>(context, attrs) {
private val actionBarShadowColor: Int = 0xA0000000.toInt()
private var actionItemIsDark: Int = 0
override fun layoutDependsOn(parent: CoordinatorLayout, child: Toolbar, dependency: View): Boolean {
return dependency.id == R.id.profileHeader
}
@SuppressLint("RestrictedApi")
override fun onDependentViewChanged(parent: CoordinatorLayout, child: Toolbar, dependency: View): Boolean {
val actionBarBackground = child.background as? ActionBarDrawable ?: return false
val bannerContainer = parent.profileBannerContainer
val bannerBottom = dependency.top + bannerContainer.height
val currentOffset = bannerBottom - child.bottom
val maxOffset = (bannerContainer.height - child.bottom).toFloat()
val factor = (1 - currentOffset / maxOffset).coerceIn(0f, 1f)
actionBarBackground.factor = factor
actionBarBackground.outlineAlphaFactor = factor
val colorPrimary = actionBarBackground.color
val currentActionBarColor = ArgbEvaluator.getInstance().evaluate(factor, actionBarShadowColor,
colorPrimary) as Int
val actionItemIsDark = if (ThemeUtils.isLightColor(currentActionBarColor)) 1 else -1
if (this.actionItemIsDark != actionItemIsDark) {
ThemeUtils.applyToolbarItemColor(parent.context, child, currentActionBarColor)
}
this.actionItemIsDark = actionItemIsDark
return true
}
} }

View File

@ -117,7 +117,12 @@
</FrameLayout> </FrameLayout>
<!-- Don't change view order, since this view consumes window insets--> <View
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.StatusBarBehavior"
tools:layout_height="25dp"/>
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -39,7 +39,7 @@
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>
<View <View
android:id="@+id/profileNameBackground" android:id="@+id/profileHeaderBackground"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@+id/countsContainer" app:layout_constraintBottom_toBottomOf="@+id/countsContainer"