forked from organicmaps/organicmaps
[android] Implemented multiline layout manager and item decorator for transit view
This commit is contained in:
parent
31fdc5bc23
commit
72b70678a6
7 changed files with 205 additions and 32 deletions
8
android/res/drawable/dot_divider.xml
Normal file
8
android/res/drawable/dot_divider.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size android:width="4dp"
|
||||
android:height="4dp"/>
|
||||
|
||||
<solid android:color="?secondary"/>
|
||||
</shape>
|
|
@ -9,7 +9,7 @@
|
|||
android:paddingStart="@dimen/margin_base"
|
||||
android:paddingRight="@dimen/margin_base"
|
||||
android:paddingEnd="@dimen/margin_base"
|
||||
android:paddingBottom="@dimen/margin_base"
|
||||
android:paddingBottom="@dimen/margin_half"
|
||||
android:paddingTop="@dimen/margin_half_plus">
|
||||
<TextView
|
||||
android:id="@+id/total_time"
|
||||
|
|
|
@ -80,8 +80,9 @@
|
|||
<dimen name="routing_shadow_bottom_margin">2dp</dimen>
|
||||
<dimen name="routing_shadow_bottom_margin_dragging">4dp</dimen>
|
||||
<dimen name="routing_turns_padding">8dp</dimen>
|
||||
<dimen name="routing_transit_step_corner_radius">2dp</dimen>
|
||||
<dimen name="routing_transit_step_corner_radius">4dp</dimen>
|
||||
<dimen name="routing_transit_step_min_height">20dp</dimen>
|
||||
<dimen name="routing_transit_setp_min_acceptable_with">104dp</dimen>
|
||||
|
||||
<!-- Bottom menu -->
|
||||
<dimen name="menu_line_height">48dp</dimen>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.mapswithme.maps.routing;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.location.Location;
|
||||
|
@ -30,7 +31,8 @@ import com.mapswithme.maps.taxi.TaxiInfo;
|
|||
import com.mapswithme.maps.taxi.TaxiLinks;
|
||||
import com.mapswithme.maps.taxi.TaxiManager;
|
||||
import com.mapswithme.maps.widget.DotPager;
|
||||
import com.mapswithme.maps.widget.recycler.TagLayoutManager;
|
||||
import com.mapswithme.maps.widget.recycler.DotDividerItemDecoration;
|
||||
import com.mapswithme.maps.widget.recycler.MultilineLayoutManager;
|
||||
import com.mapswithme.util.Graphics;
|
||||
import com.mapswithme.util.UiUtils;
|
||||
import com.mapswithme.util.Utils;
|
||||
|
@ -72,7 +74,8 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
|||
private final View mActionButton;
|
||||
@NonNull
|
||||
private final ImageView mActionIcon;
|
||||
|
||||
@NonNull
|
||||
private final DotDividerItemDecoration mTransitViewDecorator;
|
||||
@Nullable
|
||||
private TaxiInfo mTaxiInfo;
|
||||
@Nullable
|
||||
|
@ -138,6 +141,10 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
|||
mActionIcon = (ImageView) mActionButton.findViewById(R.id.iv__icon);
|
||||
UiUtils.hide(mAltitudeChartFrame, mTaxiFrame, mActionFrame);
|
||||
mListener = listener;
|
||||
Drawable dividerDrawable = ContextCompat.getDrawable(mContext, R.drawable.dot_divider);
|
||||
Resources res = mContext.getResources();
|
||||
mTransitViewDecorator = new DotDividerItemDecoration(dividerDrawable, res.getDimensionPixelSize(R.dimen.margin_base),
|
||||
res.getDimensionPixelSize(R.dimen.margin_half));
|
||||
}
|
||||
|
||||
void showAltitudeChartAndRoutingDetails()
|
||||
|
@ -188,9 +195,10 @@ final class RoutingBottomMenuController implements View.OnClickListener
|
|||
UiUtils.show(mTransitFrame);
|
||||
RecyclerView rv = (RecyclerView) mTransitFrame.findViewById(R.id.transit_recycler_view);
|
||||
TransitStepAdapter adapter = new TransitStepAdapter();
|
||||
rv.setLayoutManager(new TagLayoutManager());
|
||||
rv.setLayoutManager(new MultilineLayoutManager());
|
||||
rv.setNestedScrollingEnabled(false);
|
||||
// TODO: make dot decorator.
|
||||
rv.removeItemDecoration(mTransitViewDecorator);
|
||||
rv.addItemDecoration(mTransitViewDecorator);
|
||||
rv.setAdapter(adapter);
|
||||
adapter.setItems(info.getTransitSteps());
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.mapswithme.maps.routing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
|
@ -11,22 +10,25 @@ import android.graphics.RectF;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.Dimension;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import com.mapswithme.maps.R;
|
||||
import com.mapswithme.maps.widget.recycler.MultilineLayoutManager;
|
||||
import com.mapswithme.util.ThemeUtils;
|
||||
|
||||
/**
|
||||
* Represents a specific transit step. It displays a transit info, such as a number, color, etc., for
|
||||
* the specific transit type: pedestrian, rail, metro, etc.
|
||||
*/
|
||||
public class TransitStepView extends View
|
||||
public class TransitStepView extends View implements MultilineLayoutManager.SqueezingInterface
|
||||
{
|
||||
@Nullable
|
||||
private Drawable mDrawable;
|
||||
|
@ -39,10 +41,13 @@ public class TransitStepView extends View
|
|||
@NonNull
|
||||
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@NonNull
|
||||
private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int mBackgroundCornerRadius;
|
||||
@Nullable
|
||||
private TransitStepInfo mStepInfo;
|
||||
private String mText;
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@NonNull
|
||||
private TransitStepType mStepType;
|
||||
|
||||
public TransitStepView(Context context, @Nullable AttributeSet attrs)
|
||||
{
|
||||
|
@ -73,14 +78,16 @@ public class TransitStepView extends View
|
|||
mTextPaint.setTextSize(textSize);
|
||||
mTextPaint.setColor(textColor);
|
||||
mDrawable = a.getDrawable(R.styleable.TransitStepView_android_drawable);
|
||||
mStepType = TransitStepType.PEDESTRIAN;
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void setTransitStepInfo(@NonNull TransitStepInfo info)
|
||||
{
|
||||
mStepInfo = info;
|
||||
mDrawable = getResources().getDrawable(mStepInfo.getType().getDrawable());
|
||||
mBackgroundPaint.setColor(mStepInfo.getColor());
|
||||
mStepType = info.getType();
|
||||
mDrawable = getResources().getDrawable(mStepType.getDrawable());
|
||||
mBackgroundPaint.setColor(info.getColor());
|
||||
mText = info.getNumber();
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
|
@ -96,11 +103,10 @@ public class TransitStepView extends View
|
|||
width += mDrawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
if (mStepInfo != null && !TextUtils.isEmpty(mStepInfo.getNumber()))
|
||||
if (!TextUtils.isEmpty(mText))
|
||||
{
|
||||
String text = mStepInfo.getNumber();
|
||||
mTextPaint.getTextBounds(text, 0, text.length(), mTextBounds);
|
||||
width += (mDrawable != null ? getPaddingLeft(): 0) + mTextPaint.measureText(text);
|
||||
mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
|
||||
width += (mDrawable != null ? getPaddingLeft() : 0) + mTextPaint.measureText(mText);
|
||||
if (height == 0)
|
||||
height = getPaddingTop() + mTextBounds.height() + getPaddingBottom();
|
||||
}
|
||||
|
@ -110,6 +116,19 @@ public class TransitStepView extends View
|
|||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void squeezeTo(@Dimension int width)
|
||||
{
|
||||
int tSize = width - 2 * getPaddingLeft() - getPaddingRight() - mDrawableBounds.width();
|
||||
mText = TextUtils.ellipsize(mText, mTextPaint, tSize, TextUtils.TruncateAt.END).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumAcceptableSize()
|
||||
{
|
||||
return getResources().getDimensionPixelSize(R.dimen.routing_transit_setp_min_acceptable_with);
|
||||
}
|
||||
|
||||
private void calculateDrawableBounds(int height, @NonNull Drawable drawable)
|
||||
{
|
||||
// If the clear view height, i.e. without top/bottom padding, is greater than the drawable height
|
||||
|
@ -120,7 +139,7 @@ public class TransitStepView extends View
|
|||
vPad = (clearHeight - drawable.getIntrinsicHeight()) / 2;
|
||||
mDrawableBounds.set(getPaddingLeft(), getPaddingTop() + vPad,
|
||||
drawable.getIntrinsicWidth() + getPaddingLeft(),
|
||||
getTop() + vPad + drawable.getIntrinsicHeight());
|
||||
getPaddingTop() + vPad + drawable.getIntrinsicHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,31 +151,25 @@ public class TransitStepView extends View
|
|||
mBackgroundPaint);
|
||||
}
|
||||
|
||||
if (mDrawable != null && mStepInfo != null)
|
||||
drawDrawable(mStepInfo.getType(), mDrawable, canvas);
|
||||
if (mDrawable != null)
|
||||
drawDrawable(getContext(), mStepType, mDrawable, canvas);
|
||||
|
||||
if (mStepInfo != null && !TextUtils.isEmpty(mStepInfo.getNumber()))
|
||||
if (!TextUtils.isEmpty(mText))
|
||||
{
|
||||
String text = mStepInfo.getNumber();
|
||||
int yPos = (int) ((canvas.getHeight() / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
|
||||
int xPos = mDrawable != null ? mDrawable.getBounds().right + getPaddingLeft()
|
||||
: getPaddingLeft();
|
||||
canvas.drawText(text, xPos, yPos, mTextPaint);
|
||||
canvas.drawText(mText, xPos, yPos, mTextPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawDrawable(@NonNull TransitStepType type,
|
||||
private void drawDrawable(@NonNull Context context, @NonNull TransitStepType type,
|
||||
@NonNull Drawable drawable, @NonNull Canvas canvas)
|
||||
{
|
||||
if (type == TransitStepType.PEDESTRIAN)
|
||||
{
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = getContext().getTheme();
|
||||
if (theme.resolveAttribute(R.attr.iconTint, typedValue, true))
|
||||
{
|
||||
drawable.mutate();
|
||||
DrawableCompat.setTint(drawable, typedValue.data);
|
||||
}
|
||||
drawable.mutate();
|
||||
DrawableCompat.setTint(drawable, ThemeUtils.getColor(context, R.attr.iconTint));
|
||||
}
|
||||
drawable.setBounds(mDrawableBounds);
|
||||
drawable.draw(canvas);
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package com.mapswithme.maps.widget.recycler;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.Dimension;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
public class DotDividerItemDecoration extends RecyclerView.ItemDecoration
|
||||
{
|
||||
@Dimension
|
||||
private final int mHorizontalMargin;
|
||||
@Dimension
|
||||
private final int mVerticalMargin;
|
||||
@NonNull
|
||||
private final Drawable mDivider;
|
||||
|
||||
public DotDividerItemDecoration(@NonNull Drawable divider, @Dimension int horizontalMargin,
|
||||
@Dimension int verticalMargin)
|
||||
{
|
||||
mDivider = divider;
|
||||
mHorizontalMargin = horizontalMargin;
|
||||
mVerticalMargin = verticalMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
|
||||
{
|
||||
super.getItemOffsets(outRect, view, parent, state);
|
||||
outRect.right = mHorizontalMargin;
|
||||
outRect.bottom = mVerticalMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
|
||||
{
|
||||
if (state.isMeasuring())
|
||||
return;
|
||||
|
||||
int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount - 1; i++)
|
||||
{
|
||||
View child = parent.getChildAt(i);
|
||||
|
||||
int centerX = mHorizontalMargin / 2 + child.getRight();
|
||||
int centerY = child.getHeight() / 2 + child.getTop();
|
||||
int left = centerX - mDivider.getIntrinsicWidth() / 2;
|
||||
int right = left + mDivider.getIntrinsicWidth();
|
||||
int top = centerY - mDivider.getIntrinsicHeight() / 2;
|
||||
int bottom = top + mDivider.getIntrinsicHeight();
|
||||
|
||||
mDivider.setBounds(left, top, right, bottom);
|
||||
|
||||
mDivider.draw(c);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.mapswithme.maps.widget.recycler;
|
||||
|
||||
import android.support.annotation.Dimension;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
public class MultilineLayoutManager extends RecyclerView.LayoutManager
|
||||
{
|
||||
public MultilineLayoutManager()
|
||||
{
|
||||
setAutoMeasureEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.LayoutParams generateDefaultLayoutParams()
|
||||
{
|
||||
return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
RecyclerView.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
|
||||
{
|
||||
detachAndScrapAttachedViews(recycler);
|
||||
|
||||
int widthUsed = 0;
|
||||
int heightUsed = 0;
|
||||
int lineHeight = 0;
|
||||
int itemsCountOneLine = 0;
|
||||
for (int i = 0; i < getItemCount(); i++)
|
||||
{
|
||||
View child = recycler.getViewForPosition(i);
|
||||
addView(child);
|
||||
measureChildWithMargins(child, widthUsed, heightUsed);
|
||||
int width = getDecoratedMeasuredWidth(child);
|
||||
if (width > getWidth() - widthUsed)
|
||||
width = squeezeChildIntoLine(widthUsed, heightUsed, child);
|
||||
int height = getDecoratedMeasuredHeight(child);
|
||||
lineHeight = Math.max(lineHeight, height);
|
||||
if (widthUsed + width > getWidth())
|
||||
{
|
||||
widthUsed = 0;
|
||||
if (itemsCountOneLine > 0)
|
||||
{
|
||||
itemsCountOneLine = 0;
|
||||
heightUsed += lineHeight;
|
||||
child.forceLayout();
|
||||
measureChildWithMargins(child, widthUsed, heightUsed);
|
||||
width = getDecoratedMeasuredWidth(child);
|
||||
if (width > getWidth() - widthUsed)
|
||||
width = squeezeChildIntoLine(widthUsed, heightUsed, child);
|
||||
height = getDecoratedMeasuredHeight(child);
|
||||
}
|
||||
lineHeight = 0;
|
||||
}
|
||||
layoutDecorated(child, widthUsed, heightUsed, widthUsed + width, heightUsed + height);
|
||||
widthUsed += width;
|
||||
itemsCountOneLine++;
|
||||
}
|
||||
}
|
||||
|
||||
private int squeezeChildIntoLine(int widthUsed, int heightUsed, @NonNull View child)
|
||||
{
|
||||
if (!(child instanceof SqueezingInterface))
|
||||
return getDecoratedMeasuredWidth(child);
|
||||
|
||||
int availableWidth = getWidth() - widthUsed - getDecoratedRight(child);
|
||||
if (availableWidth > ((SqueezingInterface) child).getMinimumAcceptableSize())
|
||||
{
|
||||
((SqueezingInterface) child).squeezeTo(availableWidth);
|
||||
child.forceLayout();
|
||||
measureChildWithMargins(child, widthUsed, heightUsed);
|
||||
}
|
||||
return getDecoratedMeasuredWidth(child);
|
||||
}
|
||||
|
||||
public interface SqueezingInterface
|
||||
{
|
||||
void squeezeTo(@Dimension int width);
|
||||
@Dimension
|
||||
int getMinimumAcceptableSize();
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue