implementing conversation info ui

This commit is contained in:
Mariotaku Lee 2017-02-20 23:13:48 +08:00
parent 2507d91949
commit a8c5fca745
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
21 changed files with 596 additions and 52 deletions

View File

@ -0,0 +1,31 @@
/*
* 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.extension
import android.support.test.runner.AndroidJUnit4
import org.junit.runner.RunWith
/**
* Created by mariotaku on 2017/2/20.
*/
@RunWith(AndroidJUnit4::class)
class ViewExtensionsKtTest {
}

View File

@ -81,6 +81,7 @@ public class ShapedImageView extends ImageView {
private float mShadowRadius;
private int mStyle;
private float mCornerRadius, mCornerRadiusRatio;
private boolean mDrawShadow;
private RectF mTransitionSource, mTransitionDestination;
private int mStrokeWidth, mBorderAlpha;
private int[] mBorderColors;
@ -126,6 +127,7 @@ public class ShapedImageView extends ImageView {
setStyle(shapeStyle);
setCornerRadius(a.getDimension(R.styleable.ShapedImageView_sivCornerRadius, 0));
setCornerRadiusRatio(a.getFraction(R.styleable.ShapedImageView_sivCornerRadiusRatio, 1, 1, -1));
setDrawShadow(a.getBoolean(R.styleable.ShapedImageView_sivDrawShadow, true));
if (useOutline()) {
if (a.hasValue(R.styleable.ShapedImageView_sivElevation)) {
@ -151,7 +153,7 @@ public class ShapedImageView extends ImageView {
* @param dest The destination bound on the canvas.
*/
public void drawBitmapWithCircleOnCanvas(Bitmap bitmap, Canvas canvas,
RectF source, @NonNull RectF dest) {
RectF source, @NonNull RectF dest) {
if (bitmap == null) {
if (getStyle() == SHAPE_CIRCLE) {
canvas.drawCircle(dest.centerX(), dest.centerY(), Math.min(dest.width(), dest.height()) / 2f,
@ -265,6 +267,10 @@ public class ShapedImageView extends ImageView {
mCornerRadiusRatio = ratio;
}
public void setDrawShadow(final boolean drawShadow) {
mDrawShadow = drawShadow;
}
public void setTransitionDestination(RectF dstBounds) {
mTransitionDestination = dstBounds;
}
@ -423,7 +429,7 @@ public class ShapedImageView extends ImageView {
private void initOutlineProvider() {
if (!useOutline()) return;
ViewSupport.setClipToOutline(this, true);
ViewSupport.setOutlineProvider(this, new CircularOutlineProvider());
ViewSupport.setOutlineProvider(this, new CircularOutlineProvider(mDrawShadow));
}
private void setBorderColorsInternal(int alpha, int... colors) {
@ -504,6 +510,12 @@ public class ShapedImageView extends ImageView {
}
private static class CircularOutlineProvider extends ViewOutlineProviderCompat {
boolean drawShadow;
public CircularOutlineProvider(final boolean drawShadow) {
this.drawShadow = drawShadow;
}
@Override
public void getOutline(View view, OutlineCompat outline) {
final int viewWidth = view.getWidth(), viewHeight = view.getHeight();
@ -523,6 +535,11 @@ public class ShapedImageView extends ImageView {
final float radius = imageView.getCalculatedCornerRadius();
outline.setRoundRect(contentLeft, contentTop, contentRight, contentBottom, radius);
}
if (drawShadow) {
outline.setAlpha(1);
} else {
outline.setAlpha(0);
}
}
}
}

View File

@ -26,7 +26,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.util.view.display
import org.mariotaku.twidere.extension.view.holder.display
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
class SimpleParcelableUserListsAdapter(

View File

@ -0,0 +1,29 @@
/*
* 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.extension
import android.graphics.Point
import android.graphics.Rect
/**
* Created by mariotaku on 2017/2/20.
*/
val Rect.origin: Point get() = Point(left, top)

View File

@ -0,0 +1,69 @@
/*
* 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.extension
import android.graphics.Rect
import android.graphics.RectF
import android.support.annotation.MainThread
import android.view.View
private val tempLocation = IntArray(2)
private val tempRect = Rect()
@MainThread
fun View.getBounds(rect: RectF) {
rect.set(x, y, x + width, y + height)
}
@MainThread
fun View.getFrame(rect: Rect) {
rect.set(left, top, right, bottom)
}
@MainThread
fun View.getFrameRelatedTo(rect: Rect, view: View? = null) {
if (view != null) {
view.getFrame(tempRect)
offsetToRoot(view, tempRect)
}
this.getFrame(rect)
offsetToRoot(this, rect)
if (view != null) {
rect.offset(-tempRect.left, -tempRect.top)
}
}
fun View.getLocationOnScreen(rect: Rect) {
getLocationOnScreen(tempLocation)
rect.set(tempLocation[0], tempLocation[1], tempLocation[0] + width, tempLocation[1] + height)
}
fun View.getLocationInWindow(rect: Rect) {
getLocationInWindow(tempLocation)
rect.set(tempLocation[0], tempLocation[1], tempLocation[0] + width, tempLocation[1] + height)
}
private fun offsetToRoot(view: View, rect: Rect) {
var parent = view.parent as? View
while (parent != null) {
rect.offset(parent.left, parent.top)
parent = parent.parent as? View
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.extension.view.holder
import android.view.View
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.util.MediaLoaderWrapper
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
fun SimpleUserListViewHolder.display(userList: ParcelableUserList, mediaLoader: MediaLoaderWrapper,
userColorNameManager: UserColorNameManager, displayProfileImage: Boolean) {
nameView.text = userList.name
createdByView.text = createdByView.context.getString(R.string.created_by,
userColorNameManager.getDisplayName(userList, false))
profileImageView.visibility = if (displayProfileImage) View.VISIBLE else View.GONE
if (displayProfileImage) {
mediaLoader.displayProfileImage(profileImageView, userList)
} else {
mediaLoader.cancelDisplayTask(profileImageView)
}
}

View File

@ -40,7 +40,6 @@ import org.mariotaku.ktextension.coerceInOr
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_GAP
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_STATUS
@ -193,7 +192,7 @@ abstract class AbsActivitiesFragment protected constructor() :
}
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler, keyCode: Int, repeatCount: Int,
event: KeyEvent, metaState: Int): Boolean {
event: KeyEvent, metaState: Int): Boolean {
return navigationHelper.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState)
}
@ -305,7 +304,7 @@ abstract class AbsActivitiesFragment protected constructor() :
override fun onGapClick(holder: GapViewHolder, position: Int) {
val activity = adapter.getActivity(position) ?: return
DebugLog.v(TwidereConstants.LOGTAG, "Load activity gap $activity")
DebugLog.v(msg = "Load activity gap $activity")
val accountIds = arrayOf(activity.account_key)
val maxIds = arrayOf(activity.min_position)
val maxSortIds = longArrayOf(activity.min_sort_position)
@ -451,7 +450,7 @@ abstract class AbsActivitiesFragment protected constructor() :
protected abstract fun hasMoreData(data: List<ParcelableActivity>?): Boolean
protected abstract fun onCreateActivitiesLoader(context: Context, args: Bundle,
fromUser: Boolean): Loader<List<ParcelableActivity>>
fromUser: Boolean): Loader<List<ParcelableActivity>>
protected abstract fun onContentLoaded(loader: Loader<List<ParcelableActivity>>, data: List<ParcelableActivity>?)
@ -525,7 +524,7 @@ abstract class AbsActivitiesFragment protected constructor() :
override fun createItemDecoration(context: Context, recyclerView: RecyclerView,
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
val itemDecoration = object : DividerItemDecoration(context,
(recyclerView.layoutManager as LinearLayoutManager).orientation) {
override fun isDividerEnabled(childPos: Int): Boolean {

View File

@ -39,7 +39,6 @@ import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.activity.AccountSelectorActivity
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration
@ -353,7 +352,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
override fun onGapClick(holder: GapViewHolder, position: Int) {
val adapter = this.adapter
val status = adapter.getStatus(position) ?: return
DebugLog.v(TwidereConstants.LOGTAG, "Load activity gap " + status)
DebugLog.v(msg = "Load activity gap $status")
adapter.addGapLoadingId(ObjectId(status.account_key, status.id))
val accountIds = arrayOf(status.account_key)
val maxIds = arrayOf<String?>(status.id)

View File

@ -19,29 +19,79 @@
package org.mariotaku.twidere.fragment.message
import android.content.Context
import android.os.Bundle
import android.support.v4.app.FragmentActivity
import android.support.v4.app.LoaderManager
import android.support.v4.content.AsyncTaskLoader
import android.support.v4.content.Loader
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.activity_home_content.view.*
import kotlinx.android.synthetic.main.fragment_messages_conversation_info.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.*
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.useCursor
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_CONVERSATION_ID
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.model.displayAvatarTo
import org.mariotaku.twidere.extension.model.getConversationName
import org.mariotaku.twidere.fragment.BaseFragment
import org.mariotaku.twidere.fragment.iface.IToolBarSupportFragment
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.ParcelableMessageConversationCursorIndices
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
/**
* Created by mariotaku on 2017/2/15.
*/
class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment {
class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
LoaderManager.LoaderCallbacks<ParcelableMessageConversation?> {
private val accountKey: UserKey get() = arguments.getParcelable(EXTRA_ACCOUNT_KEY)
private val conversationId: String get() = arguments.getString(EXTRA_CONVERSATION_ID)
override val controlBarHeight: Int get() = toolbar.measuredHeight
override var controlBarOffset: Float = 0f
override val toolbar: Toolbar
get() = toolbarLayout.toolbar
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val activity = this.activity
if (activity is AppCompatActivity) {
activity.supportActionBar?.setDisplayShowTitleEnabled(false)
}
val theme = Chameleon.getOverrideTheme(context, activity)
val profileImageStyle = preferences[profileImageStyleKey]
appBarConversationAvatar.style = profileImageStyle
conversationAvatar.style = profileImageStyle
val avatarBackground = ChameleonUtils.getColorDependent(theme.colorToolbar)
appBarConversationAvatar.setBackgroundColor(avatarBackground)
appBarConversationName.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
conversationAvatar.setBackgroundColor(avatarBackground)
conversationName.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
loaderManager.initLoader(0, null, this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_messages_conversation_info, container, false)
}
@ -49,4 +99,45 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment
override fun setupWindow(activity: FragmentActivity): Boolean {
return false
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<ParcelableMessageConversation?> {
return ConversationInfoLoader(context, accountKey, conversationId)
}
override fun onLoaderReset(loader: Loader<ParcelableMessageConversation?>?) {
}
override fun onLoadFinished(loader: Loader<ParcelableMessageConversation?>?, data: ParcelableMessageConversation?) {
if (data == null) {
activity?.finish()
return
}
val name = data.getConversationName(context, userColorNameManager, preferences[nameFirstKey]).first
data.displayAvatarTo(mediaLoader, conversationAvatar)
data.displayAvatarTo(mediaLoader, appBarConversationAvatar)
appBarConversationName.text = name
conversationName.text = name
}
class ConversationInfoLoader(
context: Context,
val accountKey: UserKey,
val conversationId: String) : AsyncTaskLoader<ParcelableMessageConversation?>(context) {
override fun loadInBackground(): ParcelableMessageConversation? {
val where = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY),
Expression.equalsArgs(Conversations.CONVERSATION_ID)).sql
val whereArgs = arrayOf(accountKey.toString(), conversationId)
context.contentResolver.query(Conversations.CONTENT_URI, Conversations.COLUMNS, where,
whereArgs, null).useCursor { cur ->
if (cur.moveToFirst()) {
return ParcelableMessageConversationCursorIndices.fromCursor(cur)
}
}
return null
}
override fun onStartLoading() {
forceLoad()
}
}
}

View File

@ -163,6 +163,10 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
addMedia.setOnClickListener {
openMediaPicker()
}
conversationTitleContainer.setOnClickListener {
val intent = IntentUtils.messageConversationInfo(accountKey, conversationId)
startActivity(intent)
}
val activity = this.activity
if (activity is AppCompatActivity) {
@ -228,11 +232,6 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.info -> {
val intent = IntentUtils.messageConversationInfo(accountKey, conversationId)
startActivity(intent)
return true
}
}
return false
}

View File

@ -15,7 +15,7 @@ import org.mariotaku.twidere.fragment.CustomTabsFragment.TabEditorDialogFragment
import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.model.tab.TabConfiguration
import org.mariotaku.twidere.util.dagger.DependencyHolder
import org.mariotaku.twidere.util.view.display
import org.mariotaku.twidere.extension.view.holder.display
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
/**

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.util
import android.util.Log
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.TwidereConstants.LOGTAG
/**
* Created by mariotaku on 2017/1/24.
@ -9,7 +10,7 @@ import org.mariotaku.twidere.BuildConfig
object DebugLog {
@JvmStatic
fun v(tag: String, msg: String, tr: Throwable? = null): Int {
fun v(tag: String = LOGTAG, msg: String, tr: Throwable? = null): Int {
if (!BuildConfig.DEBUG) return 0
if (tr != null) {
return Log.v(tag, msg, tr)
@ -19,7 +20,7 @@ object DebugLog {
}
@JvmStatic
fun d(tag: String, msg: String, tr: Throwable? = null): Int {
fun d(tag: String = LOGTAG, msg: String, tr: Throwable? = null): Int {
if (!BuildConfig.DEBUG) return 0
if (tr != null) {
return Log.d(tag, msg, tr)
@ -29,7 +30,7 @@ object DebugLog {
}
@JvmStatic
fun w(tag: String, msg: String? = null, tr: Throwable? = null): Int {
fun w(tag: String = LOGTAG, msg: String? = null, tr: Throwable? = null): Int {
if (!BuildConfig.DEBUG) return 0
if (msg != null && tr != null) {
return Log.w(tag, msg, tr)

View File

@ -0,0 +1,122 @@
/*
* 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.util.view
import android.content.Context
import android.graphics.Rect
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.view.ViewCompat
import android.util.AttributeSet
import android.view.View
import kotlinx.android.synthetic.main.fragment_messages_conversation_info.view.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.getFrame
import org.mariotaku.twidere.extension.getFrameRelatedTo
import org.mariotaku.twidere.extension.getLocationOnScreen
import org.mariotaku.twidere.extension.origin
/**
* Created by mariotaku on 2017/2/20.
*/
class ConversationInfoAvatarBehavior(
context: Context,
attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<View>(context, attrs) {
private val marginBottom: Float
private val marginStart: Float
private val appBarRect = Rect()
private val sourceRect = Rect()
private val destRect = Rect()
private val tempRect = Rect()
private var toolbarLayoutHeight = 0f
var dependentPercent: Float = 0f
private set
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.ConversationInfoAvatarBehavior)
marginBottom = a.getDimension(R.styleable.ConversationInfoAvatarBehavior_behavior_avatarMarginBottom, 0f)
marginStart = a.getDimension(R.styleable.ConversationInfoAvatarBehavior_behavior_avatarMarginStart, 0f)
a.recycle()
}
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
return dependency is AppBarLayout
}
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
val appBar = parent.getDependencies(child).first()
// Get AppBar frame (and reset origin to 0, 0)
appBar.getFrame(appBarRect)
val dependencyOrigin = appBarRect.origin
appBarRect.offsetTo(0, 0)
// Layout avatar
val left: Int
val right: Int
if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
right = appBarRect.right - Math.round(marginStart)
left = right - child.measuredWidth
} else {
left = Math.round(marginStart)
right = left + child.measuredWidth
}
val bottom = Math.round(appBarRect.bottom - marginBottom)
val top = bottom - child.measuredHeight
child.layout(left, top, right, bottom)
val toolbarLayout = appBar.toolbarLayout
toolbarLayoutHeight = toolbarLayout.height.toFloat()
child.getFrameRelatedTo(sourceRect)
appBar.conversationAvatar.getFrameRelatedTo(destRect)
return true
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
val behavior = (dependency.layoutParams as CoordinatorLayout.LayoutParams).behavior as AppBarLayout.Behavior
val toolbar = dependency.toolbar
toolbar.getLocationOnScreen(tempRect)
val percent = behavior.topAndBottomOffset / (tempRect.bottom - toolbarLayoutHeight)
child.pivotX = child.width.toFloat()
child.pivotY = child.height.toFloat()
child.scaleX = 1 - (sourceRect.width() - destRect.width()) * percent / sourceRect.width()
child.scaleY = 1 - (sourceRect.height() - destRect.height()) * percent / sourceRect.height()
child.translationX = (destRect.right - sourceRect.right) * percent
child.translationY = (destRect.bottom - sourceRect.bottom) * percent
if (percent >= 1) {
child.visibility = View.INVISIBLE
dependency.conversationAvatar.visibility = View.VISIBLE
} else {
child.visibility = View.VISIBLE
dependency.conversationAvatar.visibility = View.INVISIBLE
}
dependentPercent = percent
return true
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.util.view
import android.content.Context
import android.graphics.Rect
import android.support.design.widget.CoordinatorLayout
import android.support.v4.view.ViewCompat
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.getFrame
import org.mariotaku.twidere.extension.getFrameRelatedTo
/**
* Created by mariotaku on 2017/2/20.
*/
class ConversationInfoTitleBehavior(
context: Context,
attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<TextView>(context, attrs) {
private val marginBottom: Float
private val marginStart: Float
private val avatarRect = Rect()
private val sourceRect = Rect()
private val destRect = Rect()
private var sourceSize: Float = Float.NaN
private var destSize: Float = Float.NaN
private var viewLaidOut: Boolean = false
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.ConversationInfoTitleBehavior)
marginBottom = a.getDimension(R.styleable.ConversationInfoTitleBehavior_behavior_titleMarginBottom, 0f)
marginStart = a.getDimension(R.styleable.ConversationInfoTitleBehavior_behavior_titleMarginStart, 0f)
a.recycle()
}
override fun layoutDependsOn(parent: CoordinatorLayout, child: TextView, dependency: View): Boolean {
return dependency.id == R.id.appBarConversationAvatar
}
override fun onLayoutChild(parent: CoordinatorLayout, child: TextView, layoutDirection: Int): Boolean {
val conversationAvatar = parent.getDependencies(child).first()
val appBar = conversationAvatar.parent as View
conversationAvatar.getFrame(avatarRect)
if (!viewLaidOut) {
sourceSize = child.textSize
destSize = parent.conversationName.textSize
viewLaidOut = true
}
if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
child.right = Math.round(avatarRect.left - marginStart)
child.left = child.right - child.measuredWidth
} else {
child.left = Math.round(avatarRect.right + marginStart)
child.right = child.left + child.measuredWidth
}
child.bottom = Math.round(avatarRect.centerY() - marginBottom)
child.top = child.bottom - child.measuredHeight
child.getFrameRelatedTo(sourceRect)
appBar.conversationName.getFrameRelatedTo(destRect)
return true
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: TextView, dependency: View): Boolean {
val behavior = (dependency.layoutParams as CoordinatorLayout.LayoutParams).behavior as ConversationInfoAvatarBehavior
val percent = behavior.dependentPercent
child.pivotX = child.width.toFloat()
child.pivotY = child.height.toFloat()
child.translationX = (destRect.left - sourceRect.left) * percent
child.translationY = (destRect.top - sourceRect.top) * percent
child.setTextSize(TypedValue.COMPLEX_UNIT_PX, sourceSize + (destSize - sourceSize) * percent)
if (percent >= 1) {
child.visibility = View.INVISIBLE
parent.conversationName.visibility = View.VISIBLE
} else {
child.visibility = View.VISIBLE
parent.conversationName.visibility = View.INVISIBLE
}
return true
}
}

View File

@ -1,22 +0,0 @@
package org.mariotaku.twidere.util.view
import android.view.View
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.util.MediaLoaderWrapper
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
fun SimpleUserListViewHolder.display(userList: ParcelableUserList, mediaLoader: MediaLoaderWrapper,
userColorNameManager: UserColorNameManager,
displayProfileImage: Boolean) {
nameView.text = userList.name
createdByView.text = createdByView.context.getString(R.string.created_by,
userColorNameManager.getDisplayName(userList, false))
profileImageView.visibility = if (displayProfileImage) View.VISIBLE else View.GONE
if (displayProfileImage) {
mediaLoader.displayProfileImage(profileImageView, userList)
} else {
mediaLoader.cancelDisplayTask(profileImageView)
}
}

View File

@ -167,6 +167,7 @@
android:background="?colorToolbar"
android:elevation="@dimen/toolbar_elevation"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:popupTheme="?actionBarPopupTheme"
tools:elevation="0dp">

View File

@ -10,7 +10,8 @@
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_height="180dp"
android:background="?colorToolbar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
@ -18,14 +19,23 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:titleEnabled="false">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
android:background="?colorToolbar"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_collapseMode="pin">
<include
layout="@layout/layout_toolbar_message_conversation_title"
tools:visibility="gone"/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
@ -48,8 +58,42 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/element_spacing_large"
android:src="@drawable/ic_action_edit"
app:backgroundTint="?android:colorBackground"
app:layout_anchor="@+id/appBar"
app:layout_anchorGravity="bottom|end"
tools:backgroundTint="?colorAccent"/>
tools:tint="?android:colorForeground"/>
<org.mariotaku.twidere.view.ProfileImageView
android:id="@+id/appBarConversationAvatar"
android:layout_width="@dimen/icon_size_conversation_info"
android:layout_height="@dimen/icon_size_conversation_info"
app:behavior_avatarMarginBottom="@dimen/element_spacing_large"
app:behavior_avatarMarginStart="@dimen/element_spacing_large"
app:layout_behavior="org.mariotaku.twidere.util.view.ConversationInfoAvatarBehavior"
app:sivDrawShadow="false"
app:sivElevation="8dp"
app:sivShape="circle"
tools:src="@drawable/ic_profile_image_default_group"/>
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/appBarConversationName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="8dp"
android:ellipsize="end"
android:minLines="1"
android:textAppearance="?android:textAppearanceLarge"
android:textColor="?android:textColorPrimary"
android:textStyle="normal"
app:behavior_titleMarginStart="@dimen/element_spacing_normal"
app:layout_behavior="org.mariotaku.twidere.util.view.ConversationInfoTitleBehavior"
tools:text="Title"/>
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/appBarConversationSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
tools:text="Subtitle"/>
</android.support.design.widget.CoordinatorLayout>

View File

@ -21,6 +21,7 @@
<LinearLayout
android:id="@+id/conversationTitleContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -35,6 +36,7 @@
android:layout_height="match_parent"
android:layout_margin="@dimen/element_spacing_normal"
android:layout_weight="0"
app:sivShape="circle"
tools:src="@drawable/ic_profile_image_default_group"/>
<LinearLayout
@ -43,7 +45,8 @@
android:layout_marginLeft="@dimen/element_spacing_normal"
android:layout_marginStart="@dimen/element_spacing_normal"
android:layout_weight="1"
android:gravity="center_vertical">
android:gravity="center_vertical"
android:orientation="vertical">
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/conversationName"
@ -52,5 +55,13 @@
android:textAppearance="?android:textAppearanceMedium"
android:textColor="?android:textColorPrimary"
tools:text="Conversation name"/>
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/conversationSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorSecondary"
tools:text="Conversation summary"/>
</LinearLayout>
</LinearLayout>

View File

@ -20,10 +20,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/info"
android:icon="@drawable/ic_action_info"
android:title="@string/action_conversation_info"/>
<item
android:id="@+id/search"
android:enabled="@bool/debug"

View File

@ -103,6 +103,7 @@
<attr name="sivElevation" format="dimension"/>
<attr name="sivCornerRadius" format="dimension"/>
<attr name="sivCornerRadiusRatio" format="fraction"/>
<attr name="sivDrawShadow" format="boolean"/>
<attr name="sivShape"/>
</declare-styleable>
<declare-styleable name="HeaderDrawerLayout">
@ -144,6 +145,14 @@
<declare-styleable name="ProfileBannerImageView">
<attr name="bannerAspectRatio" format="fraction"/>
</declare-styleable>
<declare-styleable name="ConversationInfoAvatarBehavior">
<attr name="behavior_avatarMarginBottom" format="dimension"/>
<attr name="behavior_avatarMarginStart" format="dimension"/>
</declare-styleable>
<declare-styleable name="ConversationInfoTitleBehavior">
<attr name="behavior_titleMarginBottom" format="dimension"/>
<attr name="behavior_titleMarginStart" format="dimension"/>
</declare-styleable>
<attr name="notificationType">
<flag name="none" value="0"/>
<flag name="ringtone" value="1"/>

View File

@ -81,6 +81,7 @@
<dimen name="icon_size_profile_type_user_profile">26dp</dimen>
<dimen name="icon_size_status_profile_image">48dp</dimen>
<dimen name="icon_size_user_profile">84dp</dimen>
<dimen name="icon_size_conversation_info">64dp</dimen>
<dimen name="line_width_compose_account_profile_image">2dp</dimen>