public class

CurvingLayoutCallback

extends WearableLinearLayoutManager.LayoutCallback

 java.lang.Object

androidx.wear.widget.WearableLinearLayoutManager.LayoutCallback

↳androidx.wear.widget.CurvingLayoutCallback

Gradle dependencies

compile group: 'androidx.wear', name: 'wear', version: '1.4.0-alpha01'

  • groupId: androidx.wear
  • artifactId: wear
  • version: 1.4.0-alpha01

Artifact androidx.wear:wear:1.4.0-alpha01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.wear:wear com.android.support:wear

Androidx class mapping:

androidx.wear.widget.CurvingLayoutCallback android.support.wear.widget.CurvingLayoutCallback

Overview

An implementation of the WearableLinearLayoutManager.LayoutCallback aligning the children of the associated WearableRecyclerView along a pre-defined vertical curve.

Summary

Constructors
publicCurvingLayoutCallback(Context context)

Methods
public voidadjustAnchorOffsetXY(View child, float[] anchorOffsetXY[])

Override this method if you wish to adjust the anchor coordinates for each child view during a layout pass.

public abstract voidonLayoutFinished(View child, RecyclerView parent)

Override this method to implement custom child layout behavior on scroll.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public CurvingLayoutCallback(Context context)

Methods

public abstract void onLayoutFinished(View child, RecyclerView parent)

Override this method to implement custom child layout behavior on scroll. It is called at the end of each layout pass of the view (including scrolling) and enables you to modify any property of the child view. Examples include scaling the children based on their distance from the center of the parent, or changing the translation of the children to create an illusion of the path they are moving along.

Parameters:

child: the current child to be affected.
parent: the RecyclerView parent that this class is attached to.

public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY[])

Override this method if you wish to adjust the anchor coordinates for each child view during a layout pass. In the override set the new desired anchor coordinates in the provided array. The coordinates should be provided in relation to the child view.

Parameters:

child: The child view to which the anchor coordinates will apply.
anchorOffsetXY: The anchor coordinates for the provided child view, by default set to a pre-defined constant on the horizontal axis and half of the child height on the vertical axis (vertical center).

Source

/*
 * Copyright (C) 2017 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.
 */

package androidx.wear.widget;

import android.content.Context;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.view.View;

import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
import androidx.wear.R;

/**
 * An implementation of the {@link WearableLinearLayoutManager.LayoutCallback} aligning the children
 * of the associated {@link WearableRecyclerView} along a pre-defined vertical curve.
 */
public class CurvingLayoutCallback extends WearableLinearLayoutManager.LayoutCallback {
    private static final float EPSILON = 0.001f;

    private final Path mCurvePath;
    private final PathMeasure mPathMeasure;
    private int mCurvePathHeight;
    private int mXCurveOffset;
    private float mPathLength;
    private float mCurveBottom;
    private float mCurveTop;
    private float mLineGradient;
    private final float[] mPathPoints = new float[2];
    private final float[] mPathTangent = new float[2];
    private final float[] mAnchorOffsetXY = new float[2];

    private RecyclerView mParentView;
    private boolean mIsScreenRound;
    private int mLayoutWidth;
    private int mLayoutHeight;

    public CurvingLayoutCallback(Context context) {
        mCurvePath = new Path();
        mPathMeasure = new PathMeasure();
        mIsScreenRound = context.getResources().getConfiguration().isScreenRound();
        mXCurveOffset = context.getResources().getDimensionPixelSize(
                R.dimen.ws_wrv_curve_default_x_offset);
    }

    @Override
    public void onLayoutFinished(View child, RecyclerView parent) {
        if (mParentView != parent || (mParentView != null && (
                mParentView.getWidth() != parent.getWidth()
                        || mParentView.getHeight() != parent.getHeight()))) {
            mParentView = parent;
            mLayoutWidth = mParentView.getWidth();
            mLayoutHeight = mParentView.getHeight();
        }
        if (mIsScreenRound) {
            maybeSetUpCircularInitialLayout(mLayoutWidth, mLayoutHeight);
            mAnchorOffsetXY[0] = mXCurveOffset;
            mAnchorOffsetXY[1] = child.getHeight() / 2.0f;
            adjustAnchorOffsetXY(child, mAnchorOffsetXY);
            float minCenter = -(float) child.getHeight() / 2;
            float maxCenter = mLayoutHeight + (float) child.getHeight() / 2;
            float range = maxCenter - minCenter;
            float verticalAnchor = (float) child.getTop() + mAnchorOffsetXY[1];
            float mYScrollProgress = (verticalAnchor + Math.abs(minCenter)) / range;

            mPathMeasure.getPosTan(mYScrollProgress * mPathLength, mPathPoints, mPathTangent);

            boolean topClusterRisk =
                    Math.abs(mPathPoints[1] - mCurveBottom) < EPSILON
                            && minCenter < mPathPoints[1];
            boolean bottomClusterRisk =
                    Math.abs(mPathPoints[1] - mCurveTop) < EPSILON
                            && maxCenter > mPathPoints[1];
            // Continue offsetting the child along the straight-line part of the curve, if it
            // has not gone off the screen when it reached the end of the original curve.
            if (topClusterRisk || bottomClusterRisk) {
                mPathPoints[1] = verticalAnchor;
                mPathPoints[0] = (Math.abs(verticalAnchor) * mLineGradient);
            }

            // Offset the View to match the provided anchor point.
            int newLeft = (int) (mPathPoints[0] - mAnchorOffsetXY[0]);
            child.offsetLeftAndRight(newLeft - child.getLeft());
            float verticalTranslation = mPathPoints[1] - verticalAnchor;
            child.setTranslationY(verticalTranslation);
        } else {
            child.setTranslationY(0);
        }
    }

    /**
     * Override this method if you wish to adjust the anchor coordinates for each child view
     * during a layout pass. In the override set the new desired anchor coordinates in
     * the provided array. The coordinates should be provided in relation to the child view.
     *
     * @param child          The child view to which the anchor coordinates will apply.
     * @param anchorOffsetXY The anchor coordinates for the provided child view, by default set
     *                       to a pre-defined constant on the horizontal axis and half of the
     *                       child height on the vertical axis (vertical center).
     */
    public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) {
        return;
    }

    @VisibleForTesting
    void setRound(boolean isScreenRound) {
        mIsScreenRound = isScreenRound;
    }

    @VisibleForTesting
    void setOffset(int offset) {
        mXCurveOffset = offset;
    }

    /** Set up the initial layout for round screens. */
    private void maybeSetUpCircularInitialLayout(int width, int height) {
        // The values in this function are custom to the curve we use.
        if (mCurvePathHeight != height) {
            mCurvePathHeight = height;
            mCurveBottom = -0.048f * height;
            mCurveTop = 1.048f * height;
            mLineGradient = 0.5f / 0.048f;
            mCurvePath.reset();
            mCurvePath.moveTo(0.5f * width, mCurveBottom);
            mCurvePath.lineTo(0.34f * width, 0.075f * height);
            mCurvePath.cubicTo(
                    0.22f * width, 0.17f * height, 0.13f * width, 0.32f * height, 0.13f * width,
                    height / 2);
            mCurvePath.cubicTo(
                    0.13f * width,
                    0.68f * height,
                    0.22f * width,
                    0.83f * height,
                    0.34f * width,
                    0.925f * height);
            mCurvePath.lineTo(width / 2, mCurveTop);
            mPathMeasure.setPath(mCurvePath, false);
            mPathLength = mPathMeasure.getLength();
        }
    }
}