blob: 20c40f923107789613e69ee9a1f4fff6ee872761 [file] [log] [blame]
/*
* Copyright (C) 2016 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 android.support.v17.leanback.widget;
import static android.support.v7.widget.RecyclerView.LayoutManager;
import static android.support.v7.widget.RecyclerView.OnScrollListener;
import static android.support.v7.widget.RecyclerView.ViewHolder;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Implementation of {@link Parallax} class for {@link RecyclerView}. This class
* allows users to track position of specific views inside {@link RecyclerView} relative to
* itself. @see {@link ChildPositionProperty} for details.
*/
public class RecyclerViewParallax extends
Parallax.IntParallax<RecyclerViewParallax.ChildPositionProperty> {
RecyclerView mRecylerView;
boolean mIsVertical;
OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
updateValues();
}
};
/**
* Subclass of {@link Parallax.IntProperty}. Using this Property, users can track a
* RecylerView child's position inside recyclerview. i.e.
*
* tracking_pos = view.top + fraction * view.height() + offset
*
* This way we can track top using fraction 0 and bottom using fraction 1.
*/
public static final class ChildPositionProperty extends Parallax.IntProperty {
int mAdapterPosition;
int mViewId;
int mOffset;
float mFraction;
ChildPositionProperty(String name, int index) {
super(name, index);
}
/**
* Sets adapter position of the recyclerview child to track.
*
* @param adapterPosition Zero based position in adapter.
* @return This ChildPositionProperty object.
*/
public ChildPositionProperty adapterPosition(int adapterPosition) {
mAdapterPosition = adapterPosition;
return this;
};
/**
* Sets view Id of a descendant of recyclerview child to track.
*
* @param viewId Id of a descendant of recyclerview child.
* @return This ChildPositionProperty object.
*/
public ChildPositionProperty viewId(int viewId) {
mViewId = viewId;
return this;
}
/**
* Sets offset in pixels added to the view's start position.
*
* @param offset Offset in pixels added to the view's start position.
* @return This ChildPositionProperty object.
*/
public ChildPositionProperty offset(int offset) {
mOffset = offset;
return this;
}
/**
* Sets fraction of size to be added to view's start position. e.g. to track the
* center position of the view, use fraction 0.5; to track the end position of the view
* use fraction 1.
*
* @param fraction Fraction of size of the view.
* @return This ChildPositionProperty object.
*/
public ChildPositionProperty fraction(float fraction) {
mFraction = fraction;
return this;
}
/**
* Returns adapter position of the recyclerview child to track.
*/
public int getAdapterPosition() {
return mAdapterPosition;
}
/**
* Returns view Id of a descendant of recyclerview child to track.
*/
public int getViewId() {
return mViewId;
}
/**
* Returns offset in pixels added to the view's start position.
*/
public int getOffset() {
return mOffset;
}
/**
* Returns fraction of size to be added to view's start position. e.g. to track the
* center position of the view, use fraction 0.5; to track the end position of the view
* use fraction 1.
*/
public float getFraction() {
return mFraction;
}
void updateValue(RecyclerViewParallax source) {
RecyclerView recyclerView = source.mRecylerView;
ViewHolder viewHolder = recyclerView == null ? null
: recyclerView.findViewHolderForAdapterPosition(mAdapterPosition);
if (viewHolder == null) {
if (recyclerView == null || recyclerView.getLayoutManager().getChildCount() == 0) {
source.setPropertyValue(getIndex(), IntProperty.UNKNOWN_AFTER);
return;
}
View firstChild = recyclerView.getLayoutManager().getChildAt(0);
ViewHolder vh = recyclerView.findContainingViewHolder(firstChild);
int firstPosition = vh.getAdapterPosition();
if (firstPosition < mAdapterPosition) {
source.setPropertyValue(getIndex(), IntProperty.UNKNOWN_AFTER);
} else {
source.setPropertyValue(getIndex(), IntProperty.UNKNOWN_BEFORE);
}
} else {
View trackingView = viewHolder.itemView.findViewById(mViewId);
if (trackingView == null) {
return;
}
Rect rect = new Rect(
0, 0, trackingView.getWidth(), trackingView.getHeight());
recyclerView.offsetDescendantRectToMyCoords(trackingView, rect);
// Slide transition may change the trackingView's translationX/translationY,
// add up translation values in parent.
float tx = 0, ty = 0;
while (trackingView != recyclerView && trackingView != null) {
tx += trackingView.getTranslationX();
ty += trackingView.getTranslationY();
trackingView = (View) trackingView.getParent();
}
rect.offset((int) tx, (int) ty);
if (source.mIsVertical) {
source.setPropertyValue(getIndex(), rect.top + mOffset
+ (int) (mFraction * rect.height()));
} else {
source.setPropertyValue(getIndex(), rect.left + mOffset
+ (int) (mFraction * rect.width()));
}
}
}
}
@Override
public ChildPositionProperty createProperty(String name, int index) {
return new ChildPositionProperty(name, index);
}
@Override
public int getMaxValue() {
if (mRecylerView == null) {
return 0;
}
return mIsVertical ? mRecylerView.getHeight() : mRecylerView.getWidth();
}
/**
* Set RecyclerView that this Parallax will register onScrollListener.
* @param recyclerView RecyclerView to register onScrollListener.
*/
public void setRecyclerView(RecyclerView recyclerView) {
if (mRecylerView == recyclerView) {
return;
}
if (mRecylerView != null) {
mRecylerView.removeOnScrollListener(mOnScrollListener);
}
mRecylerView = recyclerView;
if (mRecylerView != null) {
LayoutManager.Properties properties = mRecylerView.getLayoutManager()
.getProperties(mRecylerView.getContext(), null, 0, 0);
mIsVertical = properties.orientation == RecyclerView.VERTICAL;
mRecylerView.addOnScrollListener(mOnScrollListener);
}
}
/**
* Manually update values. This is used for changes not controlled by RecyclerView. E.g.
* called by a Slide transition that changes translation of the view.
*/
@Override
public void updateValues() {
for (ChildPositionProperty prop: getProperties()) {
prop.updateValue(RecyclerViewParallax.this);
}
super.updateValues();
}
/**
* @return Currently RecylerView that the source has registered onScrollListener.
*/
public RecyclerView getRecyclerView() {
return mRecylerView;
}
}