[android] Implemented multiline layout manager and item decorator for transit view

This commit is contained in:
Александр Зацепин 2017-11-17 14:43:18 +03:00 committed by Daria Volvenkova
parent 31fdc5bc23
commit 72b70678a6
7 changed files with 205 additions and 32 deletions

View 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>

View file

@ -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"

View file

@ -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>

View file

@ -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());

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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();
}
}