diff --git a/android/res/drawable/dot_divider.xml b/android/res/drawable/dot_divider.xml new file mode 100644 index 0000000000..a235724a66 --- /dev/null +++ b/android/res/drawable/dot_divider.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android/res/layout/routing_bottom_panel_transit.xml b/android/res/layout/routing_bottom_panel_transit.xml index 2d969265be..e27cec65fc 100644 --- a/android/res/layout/routing_bottom_panel_transit.xml +++ b/android/res/layout/routing_bottom_panel_transit.xml @@ -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"> 2dp 4dp 8dp - 2dp + 4dp 20dp + 104dp 48dp diff --git a/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java b/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java index dac66fae34..e7eeec66dd 100644 --- a/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java +++ b/android/src/com/mapswithme/maps/routing/RoutingBottomMenuController.java @@ -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()); diff --git a/android/src/com/mapswithme/maps/routing/TransitStepView.java b/android/src/com/mapswithme/maps/routing/TransitStepView.java index e54e9fdd9b..f41a2d7c48 100644 --- a/android/src/com/mapswithme/maps/routing/TransitStepView.java +++ b/android/src/com/mapswithme/maps/routing/TransitStepView.java @@ -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); diff --git a/android/src/com/mapswithme/maps/widget/recycler/DotDividerItemDecoration.java b/android/src/com/mapswithme/maps/widget/recycler/DotDividerItemDecoration.java new file mode 100644 index 0000000000..7f8f6f6cb9 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/recycler/DotDividerItemDecoration.java @@ -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); + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/recycler/MultilineLayoutManager.java b/android/src/com/mapswithme/maps/widget/recycler/MultilineLayoutManager.java new file mode 100644 index 0000000000..8b5fbcd3f4 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/recycler/MultilineLayoutManager.java @@ -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(); + } +}