From 3b66320fb0884f3587d33a2e4dc458658154e184 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Sat, 21 Mar 2015 22:18:03 +0100 Subject: [PATCH] Replace ConversationIndicator with SlidingTabLayout implementation - #132 --- .../yaaic/activity/ConversationActivity.java | 92 +- .../adapter/ConversationPagerAdapter.java | 88 +- .../indicator/ConversationIndicator.java | 303 ------- .../indicator/ConversationStateProvider.java | 52 -- .../ConversationTitlePageIndicator.java | 832 ------------------ .../ConversationSelectedListener.java | 19 +- .../org/yaaic/view/ConversationTabLayout.java | 328 +++++++ .../java/org/yaaic/view/SlidingTabStrip.java | 208 +++++ app/src/main/res/layout/conversations.xml | 15 +- app/src/main/res/values/colors.xml | 2 +- 10 files changed, 603 insertions(+), 1336 deletions(-) delete mode 100644 app/src/main/java/org/yaaic/indicator/ConversationIndicator.java delete mode 100644 app/src/main/java/org/yaaic/indicator/ConversationStateProvider.java delete mode 100644 app/src/main/java/org/yaaic/indicator/ConversationTitlePageIndicator.java create mode 100644 app/src/main/java/org/yaaic/view/ConversationTabLayout.java create mode 100644 app/src/main/java/org/yaaic/view/SlidingTabStrip.java diff --git a/app/src/main/java/org/yaaic/activity/ConversationActivity.java b/app/src/main/java/org/yaaic/activity/ConversationActivity.java index a46e8fd..771a0ee 100644 --- a/app/src/main/java/org/yaaic/activity/ConversationActivity.java +++ b/app/src/main/java/org/yaaic/activity/ConversationActivity.java @@ -20,37 +20,6 @@ along with Yaaic. If not, see . */ package org.yaaic.activity; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.yaaic.R; -import org.yaaic.Yaaic; -import org.yaaic.adapter.ConversationPagerAdapter; -import org.yaaic.adapter.MessageListAdapter; -import org.yaaic.command.CommandParser; -import org.yaaic.indicator.ConversationIndicator; -import org.yaaic.indicator.ConversationTitlePageIndicator.IndicatorStyle; -import org.yaaic.irc.IRCBinder; -import org.yaaic.irc.IRCConnection; -import org.yaaic.irc.IRCService; -import org.yaaic.listener.ConversationListener; -import org.yaaic.listener.ServerListener; -import org.yaaic.listener.SpeechClickListener; -import org.yaaic.model.Broadcast; -import org.yaaic.model.Conversation; -import org.yaaic.model.Extra; -import org.yaaic.model.Message; -import org.yaaic.model.Query; -import org.yaaic.model.Scrollback; -import org.yaaic.model.Server; -import org.yaaic.model.ServerInfo; -import org.yaaic.model.Settings; -import org.yaaic.model.Status; -import org.yaaic.model.User; -import org.yaaic.receiver.ConversationReceiver; -import org.yaaic.receiver.ServerReceiver; - import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; @@ -82,6 +51,36 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Toast; +import org.yaaic.R; +import org.yaaic.Yaaic; +import org.yaaic.adapter.ConversationPagerAdapter; +import org.yaaic.adapter.MessageListAdapter; +import org.yaaic.command.CommandParser; +import org.yaaic.irc.IRCBinder; +import org.yaaic.irc.IRCConnection; +import org.yaaic.irc.IRCService; +import org.yaaic.listener.ConversationListener; +import org.yaaic.listener.ServerListener; +import org.yaaic.listener.SpeechClickListener; +import org.yaaic.model.Broadcast; +import org.yaaic.model.Conversation; +import org.yaaic.model.Extra; +import org.yaaic.model.Message; +import org.yaaic.model.Query; +import org.yaaic.model.Scrollback; +import org.yaaic.model.Server; +import org.yaaic.model.ServerInfo; +import org.yaaic.model.Settings; +import org.yaaic.model.Status; +import org.yaaic.model.User; +import org.yaaic.receiver.ConversationReceiver; +import org.yaaic.receiver.ServerReceiver; +import org.yaaic.view.ConversationTabLayout; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + /** * The server view with a scrollable list of all channels * @@ -103,8 +102,8 @@ public class ConversationActivity extends Activity implements ServiceConnection, private ServerReceiver serverReceiver; private ViewPager pager; - private ConversationIndicator indicator; private ConversationPagerAdapter pagerAdapter; + private ConversationTabLayout tabLayout; private Scrollback scrollback; @@ -190,6 +189,7 @@ public class ConversationActivity extends Activity implements ServiceConnection, ActionBar actionBar = getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setElevation(0); setTitle(server.getTitle()); @@ -205,20 +205,10 @@ public class ConversationActivity extends Activity implements ServiceConnection, pagerAdapter = new ConversationPagerAdapter(this, server); pager.setAdapter(pagerAdapter); - final float density = getResources().getDisplayMetrics().density; - - indicator = (ConversationIndicator) findViewById(R.id.titleIndicator); - indicator.setServer(server); - indicator.setTypeface(Typeface.MONOSPACE); - indicator.setViewPager(pager); - - indicator.setFooterColor(0xFF31B6E7); - indicator.setFooterLineHeight(1 * density); - indicator.setFooterIndicatorHeight(3 * density); - indicator.setFooterIndicatorStyle(IndicatorStyle.Underline); - indicator.setSelectedColor(0xFFFFFFFF); - indicator.setSelectedBold(true); - indicator.setBackgroundColor(0xFF181818); + tabLayout = (ConversationTabLayout) findViewById(R.id.indicator); + tabLayout.setViewPager(pager); + tabLayout.setSelectedIndicatorColors(getResources().getColor(R.color.accent)); + tabLayout.setDividerColors(getResources().getColor(R.color.divider)); historySize = settings.getHistorySize(); @@ -228,9 +218,6 @@ public class ConversationActivity extends Activity implements ServiceConnection, server.getConversation(ServerInfo.DEFAULT_NAME).setHistorySize(historySize); } - float fontSize = settings.getFontSize(); - indicator.setTextSize(fontSize * density); - input.setTextSize(settings.getFontSize()); input.setTypeface(Typeface.MONOSPACE); @@ -529,7 +516,8 @@ public class ConversationActivity extends Activity implements ServiceConnection, } } - indicator.updateStateColors(); + + // indicator.updateStateColors(); } /** @@ -552,6 +540,8 @@ public class ConversationActivity extends Activity implements ServiceConnection, public void createNewConversation(String target) { pagerAdapter.addConversation(server.getConversation(target)); + + tabLayout.update(); } /** @@ -565,6 +555,8 @@ public class ConversationActivity extends Activity implements ServiceConnection, if (position != -1) { pagerAdapter.removeConversation(position); } + + tabLayout.update(); } /** diff --git a/app/src/main/java/org/yaaic/adapter/ConversationPagerAdapter.java b/app/src/main/java/org/yaaic/adapter/ConversationPagerAdapter.java index 933893c..d4af785 100644 --- a/app/src/main/java/org/yaaic/adapter/ConversationPagerAdapter.java +++ b/app/src/main/java/org/yaaic/adapter/ConversationPagerAdapter.java @@ -20,15 +20,6 @@ along with Yaaic. If not, see . */ package org.yaaic.adapter; -import java.util.HashMap; -import java.util.LinkedList; - -import org.yaaic.indicator.ConversationStateProvider; -import org.yaaic.listener.MessageClickListener; -import org.yaaic.model.Conversation; -import org.yaaic.model.Server; -import org.yaaic.view.MessageListView; - import android.content.Context; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; @@ -36,18 +27,21 @@ import android.view.View; import com.viewpagerindicator.TitlePageIndicator; +import org.yaaic.listener.MessageClickListener; +import org.yaaic.model.Conversation; +import org.yaaic.model.Server; +import org.yaaic.view.MessageListView; + +import java.util.HashMap; +import java.util.LinkedList; + /** * Adapter for displaying a pager of conversations. * * @author Sebastian Kaspari */ -public class ConversationPagerAdapter extends PagerAdapter implements ConversationStateProvider +public class ConversationPagerAdapter extends PagerAdapter { - public static final int COLOR_NONE = 0x0; - public static final int COLOR_DEFAULT = 0xFFDDDDDD; - public static final int COLOR_MESSAGE = 0xFF31B6E7; - public static final int COLOR_HIGHLIGHT = 0xFFFFBB00; - private final Server server; private LinkedList conversations; private final HashMap views; @@ -283,68 +277,4 @@ public class ConversationPagerAdapter extends PagerAdapter implements Conversati return conversation.getName(); } } - - @Override - public int getColorAt(int position) - { - Conversation conversation = getItem(position); - - switch (conversation.getStatus()) { - case Conversation.STATUS_HIGHLIGHT: - return COLOR_HIGHLIGHT; - - case Conversation.STATUS_MESSAGE: - return COLOR_MESSAGE; - - default: - return COLOR_DEFAULT; - } - } - - /** - * Get the state color for all conversations lower than the given position. - */ - @Override - public int getColorForLowerThan(int position) - { - int color = COLOR_NONE; - - for (int i = 0; i < position; i++) { - int currentColor = getColorAt(i); - - if (currentColor == COLOR_HIGHLIGHT) { - return COLOR_HIGHLIGHT; - } - - if (currentColor == COLOR_MESSAGE) { - color = COLOR_MESSAGE; - } - } - - return color; - } - - /** - * Get the state color for all conversations greater than the given position. - */ - @Override - public int getColorForGreaterThan(int position) - { - int size = conversations.size(); - int color = COLOR_NONE; - - for (int i = position + 1; i < size; i++) { - int currentColor = getColorAt(i); - - if (currentColor == COLOR_HIGHLIGHT) { - return COLOR_HIGHLIGHT; - } - - if (currentColor == COLOR_MESSAGE) { - color = COLOR_MESSAGE; - } - } - - return color; - } } diff --git a/app/src/main/java/org/yaaic/indicator/ConversationIndicator.java b/app/src/main/java/org/yaaic/indicator/ConversationIndicator.java deleted file mode 100644 index 1f1dc4d..0000000 --- a/app/src/main/java/org/yaaic/indicator/ConversationIndicator.java +++ /dev/null @@ -1,303 +0,0 @@ -/* -Yaaic - Yet Another Android IRC Client - -Copyright 2009-2013 Sebastian Kaspari - -This file is part of Yaaic. - -Yaaic 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. - -Yaaic 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 Yaaic. If not, see . - */ -package org.yaaic.indicator; - -import org.yaaic.adapter.ConversationPagerAdapter; -import org.yaaic.indicator.ConversationTitlePageIndicator.IndicatorStyle; -import org.yaaic.irc.IRCService; -import org.yaaic.model.Conversation; -import org.yaaic.model.Server; -import org.yaaic.utils.DisplayUtils; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Typeface; -import android.support.v4.view.ViewPager; -import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; - -/** - * A ConversationIndicator is a group of a ConversationTitlePageIndicator in the - * center and two drawables left and right to indicate the state of the items - * that are not displayed by the ConversationTitlePageIndicator. - * - * @author Sebastian Kaspari - */ -public class ConversationIndicator extends FrameLayout implements OnPageChangeListener { - private Server server; - private ViewPager pager; - - private View leftIndicatorView; - private View rightIndicatorView; - - private ConversationTitlePageIndicator titleIndicator; - private ConversationStateProvider stateProvider; - - /** - * Create a new {@link ConversationIndicator} instance for the given context. - * - * @param context - */ - public ConversationIndicator(Context context) { - super(context); - - init(); - } - - /** - * Create a new {@link ConversationIndicator} instance for the given context - * and attribute set. - * - * @param context - * @param attrs - */ - public ConversationIndicator(Context context, AttributeSet attrs) { - super(context, attrs); - - init(); - } - - /** - * Initialize the indicator view. - */ - public void init() { - titleIndicator = new ConversationTitlePageIndicator(getContext()); - titleIndicator.setLayoutParams( - new LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT, - Gravity.CENTER - ) - ); - - int indicatorWidth = DisplayUtils.convertToPixels(getContext(), 5); - int indicatorHeight = DisplayUtils.convertToPixels(getContext(), 5); - - leftIndicatorView = new View(getContext()); - leftIndicatorView.setLayoutParams( - new LayoutParams( - indicatorWidth, - indicatorHeight, - Gravity.LEFT | Gravity.BOTTOM - ) - ); - leftIndicatorView.setVisibility(View.INVISIBLE); - - rightIndicatorView = new View(getContext()); - rightIndicatorView.setLayoutParams( - new LayoutParams( - indicatorWidth, - indicatorHeight, - Gravity.RIGHT | Gravity.BOTTOM - ) - ); - rightIndicatorView.setVisibility(View.INVISIBLE); - - addView(leftIndicatorView); - addView(rightIndicatorView); - addView(titleIndicator); - } - - /** - * Set the {@link Server} this indicator is used for. - * - * @param server - */ - public void setServer(Server server) { - this.server = server; - } - - /** - * Set typeface of title indicator. - * - * @param typeface - */ - public void setTypeface(Typeface typeface) - { - titleIndicator.setTypeface(typeface); - } - - /** - * Set the {@link ViewPager} this indicator is used for. - * - * @param pager - */ - public void setViewPager(ViewPager pager) - { - this.pager = pager; - - titleIndicator.setViewPager(pager); - titleIndicator.setOnPageChangeListener(this); - - stateProvider = (ConversationStateProvider) pager.getAdapter(); - } - - /** - * Set the color of the footer line. - * - * @param footerColor - */ - public void setFooterColor(int footerColor) - { - titleIndicator.setFooterColor(footerColor); - } - - /** - * Set the height of the footer line. - * - * @param footerLineHeight - */ - public void setFooterLineHeight(float footerLineHeight) - { - titleIndicator.setFooterLineHeight(footerLineHeight); - } - - /** - * Set the height of the footer indicator. - * - * @param footerTriangleHeight - */ - public void setFooterIndicatorHeight(float footerTriangleHeight) - { - titleIndicator.setFooterIndicatorHeight(footerTriangleHeight); - } - - /** - * Set the style of the footer indicator. - * - * @param indicatorStyle - */ - public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) - { - titleIndicator.setFooterIndicatorStyle(indicatorStyle); - } - - /** - * Set wether selected items should be displayed as bold text. - * - * @param selectedBold - */ - public void setSelectedBold(boolean selectedBold) - { - titleIndicator.setSelectedBold(selectedBold); - } - - /** - * Set the text color of a selected item. - * - * @param selectedColor - */ - public void setSelectedColor(int selectedColor) - { - titleIndicator.setSelectedColor(selectedColor); - } - - /** - * Set the text size for the title indicator. - * - * @param textSize - */ - public void setTextSize(float textSize) - { - titleIndicator.setTextSize(textSize); - } - - /** - * On page selected: Update states of the indicators. - */ - @Override - public void onPageSelected(int page) - { - updateStateColors(); - } - - /** - * Update the colors of the state indicators. - */ - public void updateStateColors() { - int page = pager.getCurrentItem(); - - ConversationPagerAdapter adapter = (ConversationPagerAdapter) pager.getAdapter(); - Conversation conversation = adapter.getItem(page); - - Conversation previousConversation = server.getConversation(server.getSelectedConversation()); - if (previousConversation != null) { - previousConversation.setStatus(Conversation.STATUS_DEFAULT); - } - - if (conversation.getNewMentions() > 0) { - Context context = pager.getContext(); - - Intent intent = new Intent(context, IRCService.class); - intent.setAction(IRCService.ACTION_ACK_NEW_MENTIONS); - intent.putExtra(IRCService.EXTRA_ACK_SERVERID, server.getId()); - intent.putExtra(IRCService.EXTRA_ACK_CONVTITLE, conversation.getName()); - context.startService(intent); - } - - conversation.setStatus(Conversation.STATUS_SELECTED); - server.setSelectedConversation(conversation.getName()); - - if (page - 2 >= 0) { - int color = stateProvider.getColorForLowerThan(page - 1); - leftIndicatorView.setBackgroundColor(color); - leftIndicatorView.setVisibility( - color == ConversationPagerAdapter.COLOR_NONE ? View.INVISIBLE : View.VISIBLE - ); - } else { - leftIndicatorView.setVisibility(View.INVISIBLE); - } - - if (page + 2 < adapter.getCount()) { - int color = stateProvider.getColorForGreaterThan(page + 1); - - rightIndicatorView.setBackgroundColor(color); - rightIndicatorView.setVisibility( - color == ConversationPagerAdapter.COLOR_NONE ? View.INVISIBLE : View.VISIBLE - ); - } else { - rightIndicatorView.setVisibility(View.INVISIBLE); - } - - titleIndicator.invalidate(); - } - - /** - * On scroll state of page changed. - */ - @Override - public void onPageScrollStateChanged(int page) - { - // Not used. - } - - /** - * On page scrolled. - */ - @Override - public void onPageScrolled(int arg0, float arg1, int arg2) - { - // Not used. - } -} diff --git a/app/src/main/java/org/yaaic/indicator/ConversationStateProvider.java b/app/src/main/java/org/yaaic/indicator/ConversationStateProvider.java deleted file mode 100644 index 7ba8ce9..0000000 --- a/app/src/main/java/org/yaaic/indicator/ConversationStateProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -/* -Yaaic - Yet Another Android IRC Client - -Copyright 2009-2013 Sebastian Kaspari - -This file is part of Yaaic. - -Yaaic 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. - -Yaaic 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 Yaaic. If not, see . - */ -package org.yaaic.indicator; - - -/** - * Interface for a {@link ConversationStateProvider} that provides the apropriate - * state color for a position in the pager. - */ -public interface ConversationStateProvider { - /** - * Get the state color for all positions lower than the given position. - * - * @param position - * @return - */ - public int getColorForLowerThan(int position); - - /** - * Get the state color for the given position. - * - * @param position - * @return - */ - public int getColorAt(int position); - - /** - * Get the state color for all positions greater than the given position. - * - * @param position - * @return - */ - public int getColorForGreaterThan(int position); -} diff --git a/app/src/main/java/org/yaaic/indicator/ConversationTitlePageIndicator.java b/app/src/main/java/org/yaaic/indicator/ConversationTitlePageIndicator.java deleted file mode 100644 index 3057d80..0000000 --- a/app/src/main/java/org/yaaic/indicator/ConversationTitlePageIndicator.java +++ /dev/null @@ -1,832 +0,0 @@ -/* - * Copyright (C) 2012 Sebastian Kaspari - * Copyright (C) 2011 Jake Wharton - * Copyright (C) 2011 Patrik Akerfeldt - * Copyright (C) 2011 Francisco Figueiredo Jr. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.yaaic.indicator; - -import java.util.ArrayList; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewConfigurationCompat; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; - -import com.viewpagerindicator.PageIndicator; -import com.viewpagerindicator.R; - -/** - * A TitlePageIndicator is a PageIndicator which displays the title of left view - * (if exist), the title of the current select view (centered) and the title of - * the right view (if exist). When the user scrolls the ViewPager then titles are - * also scrolled. - * - * Modified version to display the titles in different colors according to the - * state of the page. - */ -public class ConversationTitlePageIndicator extends View implements PageIndicator { - /** - * Percentage indicating what percentage of the screen width away from - * center should the underline be fully faded. A value of 0.25 means that - * halfway between the center of the screen and an edge. - */ - private static final float SELECTION_FADE_PERCENTAGE = 0.25f; - - /** - * Percentage indicating what percentage of the screen width away from - * center should the selected text bold turn off. A value of 0.05 means - * that 10% between the center and an edge. - */ - private static final float BOLD_FADE_PERCENTAGE = 0.05f; - - /** - * Title text used when no title is provided by the adapter. - */ - private static final String EMPTY_TITLE = ""; - - /** - * Interface for a callback when the center item has been clicked. - */ - public interface OnCenterItemClickListener { - /** - * Callback when the center item has been clicked. - * - * @param position Position of the current center item. - */ - void onCenterItemClick(int position); - } - - public enum IndicatorStyle { - None(0), Triangle(1), Underline(2); - - public final int value; - - private IndicatorStyle(int value) { - this.value = value; - } - - public static IndicatorStyle fromValue(int value) { - for (IndicatorStyle style : IndicatorStyle.values()) { - if (style.value == value) { - return style; - } - } - return null; - } - } - - private ViewPager mViewPager; - private ViewPager.OnPageChangeListener mListener; - private PagerAdapter mPagerAdapter; - private ConversationStateProvider mStateProvider; - private int mCurrentPage = -1; - private float mPageOffset; - private int mScrollState; - private final Paint mPaintText = new Paint(); - private boolean mBoldText; - private int mColorText; - private int mColorSelected; - private final Path mPath = new Path(); - private final Rect mBounds = new Rect(); - private final Paint mPaintFooterLine = new Paint(); - private IndicatorStyle mFooterIndicatorStyle; - private final Paint mPaintFooterIndicator = new Paint(); - private float mFooterIndicatorHeight; - private float mFooterIndicatorUnderlinePadding; - private float mFooterPadding; - private float mTitlePadding; - private float mTopPadding; - /** Left and right side padding for not active view titles. */ - private float mClipPadding; - private float mFooterLineHeight; - - private static final int INVALID_POINTER = -1; - - private int mTouchSlop; - private float mLastMotionX = -1; - private int mActivePointerId = INVALID_POINTER; - private boolean mIsDragging; - - private OnCenterItemClickListener mCenterItemClickListener; - - - public ConversationTitlePageIndicator(Context context) { - this(context, null); - } - - public ConversationTitlePageIndicator(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.vpiTitlePageIndicatorStyle); - } - - public ConversationTitlePageIndicator(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - if (isInEditMode()) { - return; - } - - //Load defaults from resources - final Resources res = getResources(); - final int defaultFooterColor = res.getColor(R.color.default_title_indicator_footer_color); - final float defaultFooterLineHeight = res.getDimension(R.dimen.default_title_indicator_footer_line_height); - final int defaultFooterIndicatorStyle = res.getInteger(R.integer.default_title_indicator_footer_indicator_style); - final float defaultFooterIndicatorHeight = res.getDimension(R.dimen.default_title_indicator_footer_indicator_height); - final float defaultFooterIndicatorUnderlinePadding = res.getDimension(R.dimen.default_title_indicator_footer_indicator_underline_padding); - final float defaultFooterPadding = res.getDimension(R.dimen.default_title_indicator_footer_padding); - final int defaultSelectedColor = res.getColor(R.color.default_title_indicator_selected_color); - final boolean defaultSelectedBold = res.getBoolean(R.bool.default_title_indicator_selected_bold); - final int defaultTextColor = res.getColor(R.color.default_title_indicator_text_color); - final float defaultTextSize = res.getDimension(R.dimen.default_title_indicator_text_size); - final float defaultTitlePadding = res.getDimension(R.dimen.default_title_indicator_title_padding); - final float defaultClipPadding = res.getDimension(R.dimen.default_title_indicator_clip_padding); - final float defaultTopPadding = res.getDimension(R.dimen.default_title_indicator_top_padding); - - //Retrieve styles attributes - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, 0); - - //Retrieve the colors to be used for this view and apply them. - mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight); - mFooterIndicatorStyle = IndicatorStyle.fromValue(a.getInteger(R.styleable.TitlePageIndicator_footerIndicatorStyle, defaultFooterIndicatorStyle)); - mFooterIndicatorHeight = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorHeight, defaultFooterIndicatorHeight); - mFooterIndicatorUnderlinePadding = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorUnderlinePadding, defaultFooterIndicatorUnderlinePadding); - mFooterPadding = a.getDimension(R.styleable.TitlePageIndicator_footerPadding, defaultFooterPadding); - mTopPadding = a.getDimension(R.styleable.TitlePageIndicator_topPadding, defaultTopPadding); - mTitlePadding = a.getDimension(R.styleable.TitlePageIndicator_titlePadding, defaultTitlePadding); - mClipPadding = a.getDimension(R.styleable.TitlePageIndicator_clipPadding, defaultClipPadding); - mColorSelected = a.getColor(R.styleable.TitlePageIndicator_selectedColor, defaultSelectedColor); - mColorText = a.getColor(R.styleable.TitlePageIndicator_android_textColor, defaultTextColor); - mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold); - - final float textSize = a.getDimension(R.styleable.TitlePageIndicator_android_textSize, defaultTextSize); - final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor); - mPaintText.setTextSize(textSize); - mPaintText.setAntiAlias(true); - mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE); - mPaintFooterLine.setStrokeWidth(mFooterLineHeight); - mPaintFooterLine.setColor(footerColor); - mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE); - mPaintFooterIndicator.setColor(footerColor); - - a.recycle(); - - final ViewConfiguration configuration = ViewConfiguration.get(context); - mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); - } - - - public int getFooterColor() { - return mPaintFooterLine.getColor(); - } - - public void setFooterColor(int footerColor) { - mPaintFooterLine.setColor(footerColor); - mPaintFooterIndicator.setColor(footerColor); - invalidate(); - } - - public float getFooterLineHeight() { - return mFooterLineHeight; - } - - public void setFooterLineHeight(float footerLineHeight) { - mFooterLineHeight = footerLineHeight; - mPaintFooterLine.setStrokeWidth(mFooterLineHeight); - invalidate(); - } - - public float getFooterIndicatorHeight() { - return mFooterIndicatorHeight; - } - - public void setFooterIndicatorHeight(float footerTriangleHeight) { - mFooterIndicatorHeight = footerTriangleHeight; - invalidate(); - } - - public float getFooterIndicatorPadding() { - return mFooterPadding; - } - - public void setFooterIndicatorPadding(float footerIndicatorPadding) { - mFooterPadding = footerIndicatorPadding; - invalidate(); - } - - public IndicatorStyle getFooterIndicatorStyle() { - return mFooterIndicatorStyle; - } - - public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) { - mFooterIndicatorStyle = indicatorStyle; - invalidate(); - } - - public int getSelectedColor() { - return mColorSelected; - } - - public void setSelectedColor(int selectedColor) { - mColorSelected = selectedColor; - invalidate(); - } - - public boolean isSelectedBold() { - return mBoldText; - } - - public void setSelectedBold(boolean selectedBold) { - mBoldText = selectedBold; - invalidate(); - } - - public int getTextColor() { - return mColorText; - } - - public void setTextColor(int textColor) { - mPaintText.setColor(textColor); - mColorText = textColor; - invalidate(); - } - - public float getTextSize() { - return mPaintText.getTextSize(); - } - - public void setTextSize(float textSize) { - mPaintText.setTextSize(textSize); - invalidate(); - } - - public float getTitlePadding() { - return this.mTitlePadding; - } - - public void setTitlePadding(float titlePadding) { - mTitlePadding = titlePadding; - invalidate(); - } - - public float getTopPadding() { - return this.mTopPadding; - } - - public void setTopPadding(float topPadding) { - mTopPadding = topPadding; - invalidate(); - } - - public float getClipPadding() { - return this.mClipPadding; - } - - public void setClipPadding(float clipPadding) { - mClipPadding = clipPadding; - invalidate(); - } - - public void setTypeface(Typeface typeface) { - mPaintText.setTypeface(typeface); - invalidate(); - } - - public Typeface getTypeface() { - return mPaintText.getTypeface(); - } - - /* - * (non-Javadoc) - * - * @see android.view.View#onDraw(android.graphics.Canvas) - */ - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mViewPager == null) { - return; - } - final int count = mViewPager.getAdapter().getCount(); - if (count == 0) { - return; - } - - // mCurrentPage is -1 on first start and after orientation changed. If so, retrieve the correct index from viewpager. - if(mCurrentPage == -1 && mViewPager != null) { - mCurrentPage = mViewPager.getCurrentItem(); - } - - //Calculate views bounds - ArrayList bounds = calculateAllBounds(mPaintText); - final int boundsSize = bounds.size(); - - //Make sure we're on a page that still exists - if (mCurrentPage >= boundsSize) { - setCurrentItem(boundsSize - 1); - return; - } - - final int countMinusOne = count - 1; - final float halfWidth = getWidth() / 2f; - final int left = getLeft(); - final float leftClip = left + mClipPadding; - final int width = getWidth(); - final int height = getHeight(); - final int right = left + width; - final float rightClip = right - mClipPadding; - - int page = mCurrentPage; - float offsetPercent; - if (mPageOffset <= 0.5) { - offsetPercent = mPageOffset; - } else { - page += 1; - offsetPercent = 1 - mPageOffset; - } - final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE); - final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE); - final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE; - - //Verify if the current view must be clipped to the screen - Rect curPageBound = bounds.get(mCurrentPage); - float curPageWidth = curPageBound.right - curPageBound.left; - if (curPageBound.left < leftClip) { - //Try to clip to the screen (left side) - clipViewOnTheLeft(curPageBound, curPageWidth, left); - } - if (curPageBound.right > rightClip) { - //Try to clip to the screen (right side) - clipViewOnTheRight(curPageBound, curPageWidth, right); - } - - //Left views starting from the current position - if (mCurrentPage > 0) { - for (int i = mCurrentPage - 1; i >= 0; i--) { - Rect bound = bounds.get(i); - //Is left side is outside the screen - if (bound.left < leftClip) { - int w = bound.right - bound.left; - //Try to clip to the screen (left side) - clipViewOnTheLeft(bound, w, left); - //Except if there's an intersection with the right view - Rect rightBound = bounds.get(i + 1); - //Intersection - if (bound.right + mTitlePadding > rightBound.left) { - bound.left = (int) (rightBound.left - w - mTitlePadding); - bound.right = bound.left + w; - } - } - } - } - //Right views starting from the current position - if (mCurrentPage < countMinusOne) { - for (int i = mCurrentPage + 1 ; i < count; i++) { - Rect bound = bounds.get(i); - //If right side is outside the screen - if (bound.right > rightClip) { - int w = bound.right - bound.left; - //Try to clip to the screen (right side) - clipViewOnTheRight(bound, w, right); - //Except if there's an intersection with the left view - Rect leftBound = bounds.get(i - 1); - //Intersection - if (bound.left - mTitlePadding < leftBound.right) { - bound.left = (int) (leftBound.right + mTitlePadding); - bound.right = bound.left + w; - } - } - } - } - - //Now draw views - int colorTextAlpha = mColorText >>> 24; - for (int i = 0; i < count; i++) { - //Get the title - Rect bound = bounds.get(i); - //Only if one side is visible - if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) { - final boolean currentPage = (i == page); - final CharSequence pageTitle = getTitle(i); - - //Only set bold if we are within bounds - mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText); - - //Draw text as unselected - mPaintText.setColor(currentPage ? mColorText : mStateProvider.getColorAt(i)); - if(currentPage && currentSelected) { - //Fade out/in unselected text as the selected text fades in/out - mPaintText.setAlpha(colorTextAlpha - (int)(colorTextAlpha * selectedPercent)); - } - canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText); - - //If we are within the selected bounds draw the selected text - if (currentPage && currentSelected) { - mPaintText.setColor(mColorSelected); - mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent)); - canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText); - } - } - } - - //Draw the footer line - mPath.reset(); - mPath.moveTo(0, height - mFooterLineHeight / 2f); - mPath.lineTo(width, height - mFooterLineHeight / 2f); - mPath.close(); - canvas.drawPath(mPath, mPaintFooterLine); - - switch (mFooterIndicatorStyle) { - case Triangle: - mPath.reset(); - mPath.moveTo(halfWidth, height - mFooterLineHeight - mFooterIndicatorHeight); - mPath.lineTo(halfWidth + mFooterIndicatorHeight, height - mFooterLineHeight); - mPath.lineTo(halfWidth - mFooterIndicatorHeight, height - mFooterLineHeight); - mPath.close(); - canvas.drawPath(mPath, mPaintFooterIndicator); - break; - - case Underline: - if (!currentSelected || page >= boundsSize) { - break; - } - - Rect underlineBounds = bounds.get(page); - mPath.reset(); - mPath.moveTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); - mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); - mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); - mPath.lineTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); - mPath.close(); - - mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent)); - canvas.drawPath(mPath, mPaintFooterIndicator); - mPaintFooterIndicator.setAlpha(0xFF); - break; - - case None: - // Nothing to do here. - break; - } - } - - @Override - public boolean onTouchEvent(android.view.MotionEvent ev) { - if (super.onTouchEvent(ev)) { - return true; - } - if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { - return false; - } - - final int action = ev.getAction(); - - switch (action & MotionEventCompat.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mLastMotionX = ev.getX(); - break; - - case MotionEvent.ACTION_MOVE: { - final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float x = MotionEventCompat.getX(ev, activePointerIndex); - final float deltaX = x - mLastMotionX; - - if (!mIsDragging) { - if (Math.abs(deltaX) > mTouchSlop) { - mIsDragging = true; - } - } - - if (mIsDragging) { - mLastMotionX = x; - if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) { - mViewPager.fakeDragBy(deltaX); - } - } - - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - if (!mIsDragging) { - final int count = mViewPager.getAdapter().getCount(); - final int width = getWidth(); - final float halfWidth = width / 2f; - final float sixthWidth = width / 6f; - final float leftThird = halfWidth - sixthWidth; - final float rightThird = halfWidth + sixthWidth; - final float eventX = ev.getX(); - - if (eventX < leftThird) { - if (mCurrentPage > 0) { - mViewPager.setCurrentItem(mCurrentPage - 1); - return true; - } - } else if (eventX > rightThird) { - if (mCurrentPage < count - 1) { - mViewPager.setCurrentItem(mCurrentPage + 1); - return true; - } - } else { - //Middle third - if (mCenterItemClickListener != null) { - mCenterItemClickListener.onCenterItemClick(mCurrentPage); - } - } - } - - mIsDragging = false; - mActivePointerId = INVALID_POINTER; - if (mViewPager.isFakeDragging()) { - mViewPager.endFakeDrag(); - } - break; - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int index = MotionEventCompat.getActionIndex(ev); - final float x = MotionEventCompat.getX(ev, index); - mLastMotionX = x; - mActivePointerId = MotionEventCompat.getPointerId(ev, index); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: - final int pointerIndex = MotionEventCompat.getActionIndex(ev); - final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); - if (pointerId == mActivePointerId) { - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); - } - mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); - break; - } - - return true; - }; - - /** - * Set bounds for the right textView including clip padding. - * - * @param curViewBound - * current bounds. - * @param curViewWidth - * width of the view. - */ - private void clipViewOnTheRight(Rect curViewBound, float curViewWidth, int right) { - curViewBound.right = (int) (right - mClipPadding); - curViewBound.left = (int) (curViewBound.right - curViewWidth); - } - - /** - * Set bounds for the left textView including clip padding. - * - * @param curViewBound - * current bounds. - * @param curViewWidth - * width of the view. - */ - private void clipViewOnTheLeft(Rect curViewBound, float curViewWidth, int left) { - curViewBound.left = (int) (left + mClipPadding); - curViewBound.right = (int) (mClipPadding + curViewWidth); - } - - /** - * Calculate views bounds and scroll them according to the current index - * - * @param paint - * @return - */ - private ArrayList calculateAllBounds(Paint paint) { - ArrayList list = new ArrayList(); - //For each views (If no values then add a fake one) - final int count = mViewPager.getAdapter().getCount(); - final int width = getWidth(); - final int halfWidth = width / 2; - for (int i = 0; i < count; i++) { - Rect bounds = calcBounds(i, paint); - int w = bounds.right - bounds.left; - int h = bounds.bottom - bounds.top; - bounds.left = (int)(halfWidth - (w / 2f) + ((i - mCurrentPage - mPageOffset) * width)); - bounds.right = bounds.left + w; - bounds.top = 0; - bounds.bottom = h; - list.add(bounds); - } - - return list; - } - - /** - * Calculate the bounds for a view's title - * - * @param index - * @param paint - * @return - */ - private Rect calcBounds(int index, Paint paint) { - //Calculate the text bounds - Rect bounds = new Rect(); - CharSequence title = getTitle(index); - bounds.right = (int) paint.measureText(title, 0, title.length()); - bounds.bottom = (int) (paint.descent() - paint.ascent()); - return bounds; - } - - @Override - public void setViewPager(ViewPager view) { - if (mViewPager == view) { - return; - } - if (mViewPager != null) { - mViewPager.setOnPageChangeListener(null); - } - mPagerAdapter = view.getAdapter(); - - if (!(mPagerAdapter instanceof ConversationStateProvider)) { - throw new IllegalStateException("Adapter has to implement ConversationStateProvider."); - } - - mStateProvider = (ConversationStateProvider) mPagerAdapter; - - if (mPagerAdapter == null) { - throw new IllegalStateException("ViewPager does not have adapter instance."); - } - mViewPager = view; - mViewPager.setOnPageChangeListener(this); - invalidate(); - } - - @Override - public void setViewPager(ViewPager view, int initialPosition) { - setViewPager(view); - setCurrentItem(initialPosition); - } - - @Override - public void notifyDataSetChanged() { - invalidate(); - } - - /** - * Set a callback listener for the center item click. - * - * @param listener Callback instance. - */ - public void setOnCenterItemClickListener(OnCenterItemClickListener listener) { - mCenterItemClickListener = listener; - } - - @Override - public void setCurrentItem(int item) { - if (mViewPager == null) { - throw new IllegalStateException("ViewPager has not been bound."); - } - mViewPager.setCurrentItem(item); - mCurrentPage = item; - invalidate(); - } - - @Override - public void onPageScrollStateChanged(int state) { - mScrollState = state; - - if (mListener != null) { - mListener.onPageScrollStateChanged(state); - } - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mCurrentPage = position; - mPageOffset = positionOffset; - invalidate(); - - if (mListener != null) { - mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - } - - @Override - public void onPageSelected(int position) { - if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { - mCurrentPage = position; - invalidate(); - } - - if (mListener != null) { - mListener.onPageSelected(position); - } - } - - @Override - public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { - mListener = listener; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - //Measure our width in whatever mode specified - final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); - - //Determine our height - float height = 0; - final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); - if (heightSpecMode == MeasureSpec.EXACTLY) { - //We were told how big to be - height = MeasureSpec.getSize(heightMeasureSpec); - } else { - //Calculate the text bounds - mBounds.setEmpty(); - mBounds.bottom = (int) (mPaintText.descent() - mPaintText.ascent()); - height = mBounds.bottom - mBounds.top + mFooterLineHeight + mFooterPadding + mTopPadding; - if (mFooterIndicatorStyle != IndicatorStyle.None) { - height += mFooterIndicatorHeight; - } - } - final int measuredHeight = (int)height; - - setMeasuredDimension(measuredWidth, measuredHeight); - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState savedState = (SavedState)state; - super.onRestoreInstanceState(savedState.getSuperState()); - mCurrentPage = savedState.currentPage; - requestLayout(); - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState savedState = new SavedState(superState); - savedState.currentPage = mCurrentPage; - return savedState; - } - - static class SavedState extends BaseSavedState { - int currentPage; - - public SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - currentPage = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(currentPage); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - private CharSequence getTitle(int i) { - CharSequence title = mPagerAdapter.getPageTitle(i); - if (title == null) { - title = EMPTY_TITLE; - } - return title.toString(); - } -} diff --git a/app/src/main/java/org/yaaic/listener/ConversationSelectedListener.java b/app/src/main/java/org/yaaic/listener/ConversationSelectedListener.java index 8296328..715dde1 100644 --- a/app/src/main/java/org/yaaic/listener/ConversationSelectedListener.java +++ b/app/src/main/java/org/yaaic/listener/ConversationSelectedListener.java @@ -20,18 +20,17 @@ along with Yaaic. If not, see . */ package org.yaaic.listener; -import org.yaaic.adapter.ConversationPagerAdapter; -import org.yaaic.indicator.ConversationIndicator; -import org.yaaic.irc.IRCService; -import org.yaaic.model.Channel; -import org.yaaic.model.Conversation; -import org.yaaic.model.Server; - import android.content.Context; import android.content.Intent; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.widget.TextView; +import org.yaaic.adapter.ConversationPagerAdapter; +import org.yaaic.irc.IRCService; +import org.yaaic.model.Channel; +import org.yaaic.model.Conversation; +import org.yaaic.model.Server; + /** * Listener for conversation selections. * @@ -42,7 +41,6 @@ public class ConversationSelectedListener implements OnPageChangeListener private final Context context; private final Server server; private final TextView titleView; - private final ConversationIndicator indicator; private final ConversationPagerAdapter adapter; /** @@ -51,12 +49,11 @@ public class ConversationSelectedListener implements OnPageChangeListener * @param server * @param titleView */ - public ConversationSelectedListener(Context ctx, Server server, TextView titleView, ConversationPagerAdapter adapter, ConversationIndicator indicator) + public ConversationSelectedListener(Context ctx, Server server, TextView titleView, ConversationPagerAdapter adapter) { this.context = ctx; this.server = server; this.titleView = titleView; - this.indicator = indicator; this.adapter = adapter; } @@ -98,8 +95,6 @@ public class ConversationSelectedListener implements OnPageChangeListener conversation.setStatus(Conversation.STATUS_SELECTED); server.setSelectedConversation(conversation.getName()); } - - indicator.invalidate(); } /** diff --git a/app/src/main/java/org/yaaic/view/ConversationTabLayout.java b/app/src/main/java/org/yaaic/view/ConversationTabLayout.java new file mode 100644 index 0000000..9eb49e9 --- /dev/null +++ b/app/src/main/java/org/yaaic/view/ConversationTabLayout.java @@ -0,0 +1,328 @@ +/* +Implementation based on SlidingTabLayout (Apache License, Version 2.0) +Copyright (C) 2013 The Android Open Source Project + + +Yaaic - Yet Another Android IRC Client + +Copyright 2009-2013 Sebastian Kaspari + +This file is part of Yaaic. + +Yaaic 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. + +Yaaic 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 Yaaic. If not, see . +*/ +package org.yaaic.view; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Build; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.TextView; + +/** + * To be used with ViewPager to provide a tab indicator component which give constant feedback as to + * the user's scroll progress. + *

+ * To use the component, simply add it to your view hierarchy. Then in your + * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call + * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. + *

+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors + * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The + * alternative is via the {@link TabColorizer} interface which provides you complete control over + * which color is used for any individual position. + *

+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, + * providing the layout ID of your custom layout. + */ +public class ConversationTabLayout extends HorizontalScrollView { + + /** + * Allows complete control over the colors drawn in the tab layout. Set with + * {@link #setCustomTabColorizer(TabColorizer)}. + */ + public interface TabColorizer { + + /** + * @return return the color of the indicator used when {@code position} is selected. + */ + int getIndicatorColor(int position); + + /** + * @return return the color of the divider drawn to the right of {@code position}. + */ + int getDividerColor(int position); + + } + + private static final int TITLE_OFFSET_DIPS = 24; + private static final int TAB_VIEW_PADDING_DIPS = 16; + private static final int TAB_VIEW_TEXT_SIZE_SP = 12; + + private int mTitleOffset; + + private int mTabViewLayoutId; + private int mTabViewTextViewId; + + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; + + private final SlidingTabStrip mTabStrip; + + public ConversationTabLayout(Context context) { + this(context, null); + } + + public ConversationTabLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ConversationTabLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Disable the Scroll Bar + setHorizontalScrollBarEnabled(false); + // Make sure that the Tab Strips fills this View + setFillViewport(true); + + mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); + + mTabStrip = new SlidingTabStrip(context); + addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + + /** + * Set the custom {@link TabColorizer} to be used. + * + * If you only require simple custmisation then you can use + * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve + * similar effects. + */ + public void setCustomTabColorizer(TabColorizer tabColorizer) { + mTabStrip.setCustomTabColorizer(tabColorizer); + } + + /** + * Sets the colors to be used for indicating the selected tab. These colors are treated as a + * circular array. Providing one color will mean that all tabs are indicated with the same color. + */ + public void setSelectedIndicatorColors(int... colors) { + mTabStrip.setSelectedIndicatorColors(colors); + } + + /** + * Sets the colors to be used for tab dividers. These colors are treated as a circular array. + * Providing one color will mean that all tabs are indicated with the same color. + */ + public void setDividerColors(int... colors) { + mTabStrip.setDividerColors(colors); + } + + /** + * Set the {@link ViewPager.OnPageChangeListener}. When using {@link ConversationTabLayout} you are + * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so + * that the layout can update it's scroll position correctly. + * + * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mViewPagerPageChangeListener = listener; + } + + /** + * Set the custom layout to be inflated for the tab views. + * + * @param layoutResId Layout id to be inflated + * @param textViewId id of the {@link TextView} in the inflated view + */ + public void setCustomTabView(int layoutResId, int textViewId) { + mTabViewLayoutId = layoutResId; + mTabViewTextViewId = textViewId; + } + + /** + * Sets the associated view pager. Note that the assumption here is that the pager content + * (number of tabs and tab titles) does not change after this call has been made. + */ + public void setViewPager(ViewPager viewPager) { + mTabStrip.removeAllViews(); + + mViewPager = viewPager; + if (viewPager != null) { + viewPager.setOnPageChangeListener(new InternalViewPagerListener()); + populateTabStrip(); + } + } + + public void update() { + mTabStrip.removeAllViews(); + populateTabStrip(); + } + + /** + * Create a default view to be used for tabs. This is called if a custom tab view is not set via + * {@link #setCustomTabView(int, int)}. + */ + protected TextView createDefaultTabView(Context context) { + TextView textView = new TextView(context); + textView.setGravity(Gravity.CENTER); + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); + textView.setTypeface(Typeface.DEFAULT_BOLD); + textView.setTextColor(0xFFFFFFFF); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // If we're running on Honeycomb or newer, then we can use the Theme's + // selectableItemBackground to ensure that the View has a pressed state + TypedValue outValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, + outValue, true); + textView.setBackgroundResource(outValue.resourceId); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style + textView.setAllCaps(true); + } + + int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); + textView.setPadding(padding, padding, padding, padding); + + return textView; + } + + private void populateTabStrip() { + final PagerAdapter adapter = mViewPager.getAdapter(); + final View.OnClickListener tabClickListener = new TabClickListener(); + + for (int i = 0; i < adapter.getCount(); i++) { + View tabView = null; + TextView tabTitleView = null; + + if (mTabViewLayoutId != 0) { + // If there is a custom tab view layout id set, try and inflate it + tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, + false); + tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); + } + + if (tabView == null) { + tabView = createDefaultTabView(getContext()); + } + + if (tabTitleView == null && TextView.class.isInstance(tabView)) { + tabTitleView = (TextView) tabView; + } + + tabTitleView.setText(adapter.getPageTitle(i)); + tabView.setOnClickListener(tabClickListener); + + mTabStrip.addView(tabView); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mViewPager != null) { + scrollToTab(mViewPager.getCurrentItem(), 0); + } + } + + private void scrollToTab(int tabIndex, int positionOffset) { + final int tabStripChildCount = mTabStrip.getChildCount(); + if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { + return; + } + + View selectedChild = mTabStrip.getChildAt(tabIndex); + if (selectedChild != null) { + int targetScrollX = selectedChild.getLeft() + positionOffset; + + if (tabIndex > 0 || positionOffset > 0) { + // If we're not at the first child and are mid-scroll, make sure we obey the offset + targetScrollX -= mTitleOffset; + } + + scrollTo(targetScrollX, 0); + } + } + + private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { + private int mScrollState; + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + int tabStripChildCount = mTabStrip.getChildCount(); + if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { + return; + } + + mTabStrip.onViewPagerPageChanged(position, positionOffset); + + View selectedTitle = mTabStrip.getChildAt(position); + int extraOffset = (selectedTitle != null) + ? (int) (positionOffset * selectedTitle.getWidth()) + : 0; + scrollToTab(position, extraOffset); + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, + positionOffsetPixels); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mTabStrip.onViewPagerPageChanged(position, 0f); + scrollToTab(position, 0); + } + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageSelected(position); + } + } + + } + + private class TabClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + for (int i = 0; i < mTabStrip.getChildCount(); i++) { + if (v == mTabStrip.getChildAt(i)) { + mViewPager.setCurrentItem(i); + return; + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/yaaic/view/SlidingTabStrip.java b/app/src/main/java/org/yaaic/view/SlidingTabStrip.java new file mode 100644 index 0000000..d1ece83 --- /dev/null +++ b/app/src/main/java/org/yaaic/view/SlidingTabStrip.java @@ -0,0 +1,208 @@ +package org.yaaic.view; + +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.R; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; + +class SlidingTabStrip extends LinearLayout { + + private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2; + private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; + private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8; + private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; + + private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1; + private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20; + private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f; + + private final int mBottomBorderThickness; + private final Paint mBottomBorderPaint; + + private final int mSelectedIndicatorThickness; + private final Paint mSelectedIndicatorPaint; + + private final int mDefaultBottomBorderColor; + + private final Paint mDividerPaint; + private final float mDividerHeight; + + private int mSelectedPosition; + private float mSelectionOffset; + + private ConversationTabLayout.TabColorizer mCustomTabColorizer; + private final SimpleTabColorizer mDefaultTabColorizer; + + SlidingTabStrip(Context context) { + this(context, null); + } + + SlidingTabStrip(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + + final float density = getResources().getDisplayMetrics().density; + + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true); + final int themeForegroundColor = outValue.data; + + mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, + DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); + + mDefaultTabColorizer = new SimpleTabColorizer(); + mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); + mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor, + DEFAULT_DIVIDER_COLOR_ALPHA)); + + mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); + mBottomBorderPaint = new Paint(); + mBottomBorderPaint.setColor(mDefaultBottomBorderColor); + + mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); + mSelectedIndicatorPaint = new Paint(); + + mDividerHeight = DEFAULT_DIVIDER_HEIGHT; + mDividerPaint = new Paint(); + mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density)); + } + + void setCustomTabColorizer(ConversationTabLayout.TabColorizer customTabColorizer) { + mCustomTabColorizer = customTabColorizer; + invalidate(); + } + + void setSelectedIndicatorColors(int... colors) { + // Make sure that the custom colorizer is removed + mCustomTabColorizer = null; + mDefaultTabColorizer.setIndicatorColors(colors); + invalidate(); + } + + void setDividerColors(int... colors) { + // Make sure that the custom colorizer is removed + mCustomTabColorizer = null; + mDefaultTabColorizer.setDividerColors(colors); + invalidate(); + } + + void onViewPagerPageChanged(int position, float positionOffset) { + mSelectedPosition = position; + mSelectionOffset = positionOffset; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + final int height = getHeight(); + final int childCount = getChildCount(); + final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height); + final ConversationTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null + ? mCustomTabColorizer + : mDefaultTabColorizer; + + // Thick colored underline below the current selection + if (childCount > 0) { + View selectedTitle = getChildAt(mSelectedPosition); + int left = selectedTitle.getLeft(); + int right = selectedTitle.getRight(); + int color = tabColorizer.getIndicatorColor(mSelectedPosition); + + if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { + int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); + if (color != nextColor) { + color = blendColors(nextColor, color, mSelectionOffset); + } + + // Draw the selection partway between the tabs + View nextTitle = getChildAt(mSelectedPosition + 1); + left = (int) (mSelectionOffset * nextTitle.getLeft() + + (1.0f - mSelectionOffset) * left); + right = (int) (mSelectionOffset * nextTitle.getRight() + + (1.0f - mSelectionOffset) * right); + } + + mSelectedIndicatorPaint.setColor(color); + + canvas.drawRect(left, height - mSelectedIndicatorThickness, right, + height, mSelectedIndicatorPaint); + } + + // Thin underline along the entire bottom edge + canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); + + // Vertical separators between the titles + int separatorTop = (height - dividerHeightPx) / 2; + for (int i = 0; i < childCount - 1; i++) { + View child = getChildAt(i); + mDividerPaint.setColor(tabColorizer.getDividerColor(i)); + canvas.drawLine(child.getRight(), separatorTop, child.getRight(), + separatorTop + dividerHeightPx, mDividerPaint); + } + } + + /** + * Set the alpha value of the {@code color} to be the given {@code alpha} value. + */ + private static int setColorAlpha(int color, byte alpha) { + return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + /** + * Blend {@code color1} and {@code color2} using the given ratio. + * + * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, + * 0.0 will return {@code color2}. + */ + private static int blendColors(int color1, int color2, float ratio) { + final float inverseRation = 1f - ratio; + float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); + float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); + float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); + return Color.rgb((int) r, (int) g, (int) b); + } + + private static class SimpleTabColorizer implements ConversationTabLayout.TabColorizer { + private int[] mIndicatorColors; + private int[] mDividerColors; + + @Override + public final int getIndicatorColor(int position) { + return mIndicatorColors[position % mIndicatorColors.length]; + } + + @Override + public final int getDividerColor(int position) { + return mDividerColors[position % mDividerColors.length]; + } + + void setIndicatorColors(int... colors) { + mIndicatorColors = colors; + } + + void setDividerColors(int... colors) { + mDividerColors = colors; + } + } +} diff --git a/app/src/main/res/layout/conversations.xml b/app/src/main/res/layout/conversations.xml index f16bb4e..3eb6d83 100644 --- a/app/src/main/res/layout/conversations.xml +++ b/app/src/main/res/layout/conversations.xml @@ -23,16 +23,17 @@ along with Yaaic. If not, see . xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="#ff000000"> - + android:layout_height="fill_parent"> + #212121 #FF5722 #727272 - #B6B6B6 + #CCCCCC #FFC107 #EEEEEE