I have some problem trying to control touch event propagation within my RecycleView. So I have a RecyclerView populating a set of CardViews imitating a card stack (so they overlap each other with a certain displacement, though they have different elevation value). My current problem is that each card has a button and since relative card displacement is smaller than height of the button it results in the situation that buttons are overlapping and whenever a touch event is dispatched it starts propagating from the bottom of view hierarchy (from children with highest child number).
According to articles I read (this, this and also this video) touch propagation is dependent on the order of views in parent view, so touch will first be delivered to the child with highest index, while I want the touch event to be processed only by touchables of the topmost view and RecyclerView (it also has to process drag and fling gestures). Currently I am using a fallback with cards that are aware of their position within parent's view hierarchy to prevent wrong children from processing touches but this is really ugly way to do that. My assumption is that I have to override dispatchTouchEvent method of the RecyclerView and properly dispatch a touch event only to topmost child. However, when I tried this way of doing that (which is also kind of clumsy):
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
View topChild = getChildAt(0);
for (View touchable : topChild.getTouchables()) {
int[] location = new int[2];
touchable.getLocationOnScreen(location);
RectF touchableRect = new RectF(location[0],
location[1],
location[0] + touchable.getWidth(),
location[1] + touchable.getHeight());
if (touchableRect.contains(ev.getRawX(), ev.getRawY())) {
return touchable.dispatchTouchEvent(ev);
}
}
return onTouchEvent(ev);
}
Only DOWN event was delivered to the button within a card (no click event triggered). I will appreciate any advice on the way of reversing touch event propagation order or on delegating of touch event to a specific View. Thank you very much in advance.
EDIT: This is the screenshot of how the example card stack is looking like
Example adapter code:
public class TestAdapter extends RecyclerView.Adapter {
List<Integer> items;
public TestAdapter(List<Integer> items) {
this.items = items;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.test_layout, parent, false);
return new TestHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
TestHolder currentHolder = (TestHolder) holder;
currentHolder.number.setText(Integer.toString(position));
currentHolder.tv.setTag(position);
currentHolder.tv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int pos = (int) v.getTag();
Toast.makeText(v.getContext(), Integer.toString(pos) + "clicked", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public int getItemCount() {
return items.size();
}
private class TestHolder extends RecyclerView.ViewHolder {
private TextView tv;
private TextView number;
public TestHolder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.click);
number = (TextView) itemView.findViewById(R.id.number);
}
}
}
and an example card layout:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="16dp"
android:textSize="24sp"
android:id="#+id/number"/>
<TextView android:layout_width="wrap_content"
android:layout_height="64dp"
android:gravity="center"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_margin="16dp"
android:textSize="24sp"
android:text="CLICK ME"
android:id="#+id/click"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
And here is the code that I am using now to solve the problem (this approach I do not like, I want to find better way)
public class PositionAwareCardView extends CardView {
private int mChildPosition;
public PositionAwareCardView(Context context) {
super(context);
}
public PositionAwareCardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PositionAwareCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChildPosition(int pos) {
mChildPosition = pos;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// if it's not the topmost view in view hierarchy then block touch events
return mChildPosition != 0 || super.onInterceptTouchEvent(ev);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
}
EDIT 2: I've forgotten to mention, this problem is present only on pre-Lolipop devices, it seems that starting from Lolipop, ViewGroups also take elevation into consideration while dispatching touch events
EDIT 3: Here is my current child drawing order:
#Override
protected int getChildDrawingOrder(int childCount, int i) {
return childCount - i - 1;
}
EDIT 4: Finally I was able to fix the problem thanks to user random, and this answer, the solution was extremely simple:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onInterceptTouchEvent(ev)) {
if (getChildCount() > 0) {
final float offsetX = -getChildAt(0).getLeft();
final float offsetY = -getChildAt(0).getTop();
ev.offsetLocation(offsetX, offsetY);
if (getChildAt(0).dispatchTouchEvent(ev)) {
// if touch event is consumed by the child - then just record it's coordinates
x = ev.getRawX();
y = ev.getRawY();
return true;
}
ev.offsetLocation(-offsetX, -offsetY);
}
}
return super.dispatchTouchEvent(ev);
}
CardView uses elevation API on L and before L, it changes the shadow size. dispatchTouchEvent in L respects Z ordering when iterating over children which doesn't happen in pre L.
Looking at the source code:
Pre Lollipop
ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
...
Lollipop
ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
// Check whether any clickable siblings cover the child
// view and if so keep track of the intersections. Also
// respect Z ordering when iterating over children.
ArrayList<View> orderedList = buildOrderedChildList();
final boolean useCustomOrder = orderedList == null
&& isChildrenDrawingOrderEnabled();
final int childCount = mChildrenCount;
for (int i = childCount - 1; i >= 0; i--) {
final int childIndex = useCustomOrder
? getChildDrawingOrder(childCount, i) : i;
final View sibling = (orderedList == null)
? mChildren[childIndex] : orderedList.get(childIndex);
// We care only about siblings over the child
if (sibling == child) {
break;
}
...
The child drawing order can be overridden with custom child drawing order in a ViewGroup, and with setZ(float) custom Z values set on Views.
You might want to check custom child drawing order in a ViewGroup but I think your current fix for the problem is good enough.
Did you try to set your topmost view as clickable ?
button.setClickable(true);
If this attribute is not set (as default) in the View XML it propagate the click event upwards.
But if you set it on the topmost view as true, it shouldn't propagate any event on any other view.
Related
How can achieve pinch zoom behavior like Gmail app? I've put header container in ScrollView followed by WebView. Seems It's very complex behavior.
Here is without zoom.
When we pinch Webview upper container scrolled up as per zoom:
So far here is my initials:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/white">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/appbar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#color/colorPrimary"></FrameLayout>
<WebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:scrollbars="none" />
</LinearLayout>
</ScrollView>
</RelativeLayout>
</FrameLayout>
GMail uses a Chrome WebView with pinch zoom enabled. the zoom only applies to the single thread view. WebSettings setBuiltInZoomControls() is by default false and setDisplayZoomControls() is by default true. by changing both, the zoom works and there are no zoom controls being displayed:
webview.getSettings().setBuiltInZoomControls(true);
webview.getSettings().setDisplayZoomControls(false);
and that toolbar is a transparently styled ActionBar, with style windowActionBarOverlay set true:
Flag indicating whether this window's Action Bar should overlay application content.
the ActionBar's bottom shadow is being removed in the top-most scroll position. this one listens for vertical scroll events and not for any scaling gestures. this effect works about like this (initially that shadow has to be hidden):
webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY == 0) {
/* remove the ActionBar's bottom shadow */
} else {
/* apply the ActionBar's bottom shadow */
}
}
}
depending how often the OnScrollChangeListener is being triggered, even checking for scrollY == 0 and scrollY == 1 might already suffice to switch the shadow on and off.
when scaling, this seems to be a ScaleGestureDetector.SimpleOnScaleGestureListener (see the docs), where .getScaleFactor() is being used to animate the secondary "toolbar" vertical top position, which then shoves it outside of the visible view-port. and this secondary "toolbar" appears to be a nested vertical DrawerLayout - which cannot be manually moved - that's why it moves that smooth... a DrawerLayout is not limited to be a horizontal drawer; and I think this is the answer.
Edit: I'd relatively certain now, that this is AndroidX with MDC Motion.
I think I understood your question. You want to push the subject line in the upward direction and the other emails in the downward direction when an email is being expanded. I tried to implement the idea of showing an email in the Gmail app. I think I am very close to the solution as the pushing is not smooth enough. However, I wanted to share the answer here to present my thought about your question.
I have created a GitHub repository from where you can see my implementation. I have added a readme there as well to explain the overall idea.
I tried to implement the whole thing using a RecyclerView have different ViewTypes. I have added an adapter which is like the following.
public class RecyclerViewWithHeaderFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int HEADER_VIEW = 1;
private static final int GROUPED_VIEW = 2;
private static final int EXPANDED_VIEW = 3;
private ArrayList<Integer> positionTracker; // Take any list that matches your requirement.
private Context context;
private ZoomListener zoomListener;
// Define a constructor
public RecyclerViewWithHeaderFooterAdapter(Context context, ZoomListener zoomListener) {
this.context = context;
this.zoomListener = zoomListener;
positionTracker = Utilities.populatePositionsWithDummyData();
}
// Define a ViewHolder for Header view
public class HeaderViewHolder extends ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the item
}
});
}
}
// Define a ViewHolder for Expanded view
public class ExpandedViewHolder extends ViewHolder {
public ExpandedViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the item
}
});
}
}
// Define a ViewHolder for Expanded view
public class GroupedViewHolder extends ViewHolder {
public GroupedViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the item
}
});
}
}
// And now in onCreateViewHolder you have to pass the correct view
// while populating the list item.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
if (viewType == EXPANDED_VIEW) {
v = LayoutInflater.from(context).inflate(R.layout.list_item_expanded, parent, false);
ExpandedViewHolder vh = new ExpandedViewHolder(v);
return vh;
} else if (viewType == HEADER_VIEW) {
v = LayoutInflater.from(context).inflate(R.layout.list_item_header, parent, false);
HeaderViewHolder vh = new HeaderViewHolder(v);
return vh;
} else {
v = LayoutInflater.from(context).inflate(R.layout.list_item_grouped, parent, false);
GroupedViewHolder vh = new GroupedViewHolder(v);
return vh;
}
}
// Now bind the ViewHolder in onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof ExpandedViewHolder) {
ExpandedViewHolder vh = (ExpandedViewHolder) holder;
vh.bindExpandedView(position);
} else if (holder instanceof GroupedViewHolder) {
GroupedViewHolder vh = (GroupedViewHolder) holder;
} else if (holder instanceof HeaderViewHolder) {
HeaderViewHolder vh = (HeaderViewHolder) holder;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// Now the critical part. You have return the exact item count of your list
// I've only one footer. So I returned data.size() + 1
// If you've multiple headers and footers, you've to return total count
// like, headers.size() + data.size() + footers.size()
#Override
public int getItemCount() {
return DEMO_LIST_SIZE; // Let us consider we have 6 elements. This can be replaced with email chain size
}
// Now define getItemViewType of your own.
#Override
public int getItemViewType(int position) {
if (positionTracker.get(position).equals(HEADER_VIEW)) {
// This is where we'll add the header.
return HEADER_VIEW;
} else if (positionTracker.get(position).equals(GROUPED_VIEW)) {
// This is where we'll add the header.
return GROUPED_VIEW;
} else if (positionTracker.get(position).equals(EXPANDED_VIEW)) {
// This is where we'll add the header.
return EXPANDED_VIEW;
}
return super.getItemViewType(position);
}
// So you're done with adding a footer and its action on onClick.
// Now set the default ViewHolder for NormalViewHolder
public class ViewHolder extends RecyclerView.ViewHolder {
// Define elements of a row here
public ViewHolder(View itemView) {
super(itemView);
// Find view by ID and initialize here
}
public void bindExpandedView(final int position) {
// bindExpandedView() method to implement actions
final WebView webView = itemView.findViewById(R.id.email_details_web_view);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setDisplayZoomControls(false);
webView.loadUrl("file:///android_asset/sample.html");
webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
zoomListener.onZoomListener(position);
}
});
}
}
}
And the expanded list item contains a WebView which has a wrapper which is wrap_content. You will find the following layout in the list_item_expanded.xml.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<WebView
android:id="#+id/email_details_web_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:scrollbars="none"
tools:ignore="WebViewLayout" />
</RelativeLayout>
I tried to add some dummy data for the experiment and hence the Utility class was written. The RecyclerView is set to have a reverse layout as this is the common expectation of showing a conversation in a RecyclerView.
The key idea is to scrollToPosition when the WebView is being expanded. So that it feels like the items are push upwards and downwards to accommodate the expansion. Hope you get the idea.
I am adding some screenshots here to give you an idea about what I could achieve so far.
Please note that the pushing mechanism is not smooth. I will be working on this. However, I thought I should post it here as this might help you in your thinking. I would like to suggest you clone the repository and run the application to check the overall implementation. Let me know if there is any feedback.
I tried to implement this behaviour using webview.setBuiltInZoomControls() and ScaleGestureDetector by overriding WebView and feeding all motion events to detector. It works, but scale detector and zoom work a bit different and UX turns out to be terrible.
If you look closely at Gmail zoom implementation and a webview zoom you will see they are different. I believe the Gmail zoom is based on view.setScaleX() and view.setScaleY(). You can get a basic behaviour by subclassing WebView and following this guide. You may also need to call view.setPivotX() and view.setPivotY(). Gmail implementation is more complex since it has scroll and seems to scroll the content up while you zoom in. You can try to use some library that implements the zoomable container and supports scrolling, like this one. However I was unable to make it work properly with WebView.
Overall it's a complex task and you have to play with the implementation yourself to make some compromises and get a similar but decent UX.
I have a RecyclerView which uses as its child layout a ViewGroup.
RecyclerView :
#Override
public TileDetailsHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.weather_tile_details_c, parent, Constant.SHOULD_ATTACH_NOW);
return new TileDetailsHolder(view);
}
#Override
public void onBindViewHolder(TileDetailsHolder holder, int position)
{
holder.setSavedPosition(tileList.get(position).getSavedPosition());
position);
holder.setDetailsRowList(tileList.get(position).getDetailRows());
}
XML :
<views.WeatherDetailLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/weatherDetailsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<views.WeatherDetailHeaderView
android:id="#+id/weatherDetailsHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<views.WeatherDetailDaysView
android:id="#+id/weatherDetailDays"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="#+id/detailInnerRV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_green_light"/>
</views.WeatherDetailLayout>
ViewGroup :
public class WeatherDetailLayout extends ViewGroup
{
private int width;
private int height;
private int leftPos = 0;
private int rightPos = 0;
private int topPos = 0;
private int[] bottomPos = new int[3]; //3 = number of views
public WeatherDetailLayout(Context context) { super(context); init(); }
public WeatherDetailLayout(Context context, #Nullable AttributeSet attrs)
{ super(context, attrs); init(); }
public void init()
{
width = (int) Util.getScreenWidthInPixels();
height = (int) Util.getScreenHeightInPixelsNoToolbarOrStatusbar();
rightPos = width;
bottomPos[0] = (int) (height * 0.40);
bottomPos[1] = (int) (height * 0.20);
bottomPos[2] = (int) (height * 0.40);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
for(int i = 0; i < bottomPos.length; i++)
{
getChildAt(i).layout(leftPos, topPos, rightPos, topPos + bottomPos[i]);
topPos += bottomPos[i];
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int measureSpec)
{
return resolveSizeAndState(width, measureSpec, 0);
}
private int measureHeight(int measureSpec)
{
return resolveSizeAndState(height, measureSpec, 0);
}
}
This code gives 3 views which are inside of the ViewGroup that have the same width as the screen and basically
40 percent of the total height for the first, 20 for the second and 40 for the third.
The RecyclerView is setup with a horizontal layout manager. Only one item is visible at a time. The user can then scroll to the left or the right a certain number of items let say 10. As the user scrolls the left most or right most item is removed and a new one is added depending on which way the user scrolled/swiped.
1 2 3 4 5 6 7 8 9 10
User starts at 5, scrolls to 6, 4 gets removed, 7 gets added.
The first N (usually 3 in this example) items get drawn fine. Meaning when the RecyclerView is using onCreateViewHolder. However, when the RecyclerView starts recycling views and using onBindViewHolder nothing gets shown anymore. Using log statement I can see it goes into the ViewGroup and goes onLayout and onMeasure. Everything get calculated the childGetCount method returns 3 views but nothing gets shown. Simply get the background of the activity like so.
In the above pic the layout bounds don't even get shown only the outer layout of the recyclerView gets shown. (Using the option on the phone Show layout bounds).
When I do this but instead of the custom viewgroup I use a LinearLayout but the same custom views this does not happen. It shows the views every time. I can also see that it goes into onCreateViewHolder only 3 times as well.
I am looking for any leads or answers to resolve this issue.
I am recently using CustomBottomSheetBehavior to make an googlemaps like bottom sheet behavior and it works great. I have only one problem.please look at this image
If I use it in a scrolling activity the content of tool-bar covers my list box. so I ahve to add margin-top to my list view. It works but when I draw bottomsheet up toolbar goes up and behind it, there is an empty space. This is because I have added some margin top to make my list's top visible. Is there any way to connect list's margin top to the amount of moving bottom-sheet and when It moves up decrease margin value to and when it moves down increase it?or is there any better way ?
It seems I have to develope my own TopMarginBehavior for this job but I have no idea how to do it.
thanks
Create your own class related to the behavior you want (MarginTopBehavior)
Extends it from CoordinatorLayout.Behavior
Now you have to focus on 2 methods: layoutDependsOn and onDependentViewChanged. With the first one you are selecting the view that your MarginTopBehavior is following, in this case is a NestedScrollView. With the second one you are reacting (the magic!) when the scroll get moved.
At this point you get this:
public class MarginTopBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
private FrameLayout.LayoutParams mLayoutParams;
public MarginTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
}
}
The logic to be applied in onDependentViewChanged is just this:
* Define the cap (min/max margin value) and controls when the margin value has reached one of those cap.
* Update margin value while the values are between the caps. In this point you have to implement an algorithm about what you want (parallax, linear, etc). That is what I'm calling THE_MAGIC_ECC in the next code:
public class MarginTopBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
/**
* Params of the component you want to modify the margin
*/
private FrameLayout.LayoutParams mLayoutParams;
/**
* Used to access DIMENS in your project
*/
private Context mContext;
private int mMinYvalue;
private int mMaxYValue;
public MarginTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (mLayoutParams == null) {
mLayoutParams = (FrameLayout.LayoutParams) child.getLayoutParams();
}
if (dependency.getY() <= mMinYvalue) {
mLayoutParams.setMargins(0, 0, 0, 0);
child.setLayoutParams(mLayoutParams);
return true;
}
else if (dependency.getY() > mMinYvalue && dependency.getY() <= mMaxYValue) {
int THE_MAGIC_ECC = 1 + 2 + 3;
mLayoutParams.setMargins(0, 0, 0, THE_MAGIC_ECC );
child.setLayoutParams(mLayoutParams);
return true;
}
else {
mLayoutParams.setMargins(0, 0, 0, 100);
child.setLayoutParams(mLayoutParams);
return true;
}
}
}
I want to create below layout under which item will scroll vertically. whenever we scroll up or down the centered(28) textview should be zoom in position.i found this link Gallery like view with center image zoom but they scroll horizontally using image based on their requirement. i need vertically scroll only using textview
please let me know if any body know the logic.......
First of all, thanks to #sanjay kumar had a good question.
After 2 days, I found a best solution for this question.
You can use RecyclerView and make sure the LinearLayoutManager should like this
LinearLayoutManager yourLinearLayout= new LinearLayoutManager(getApplicationContext());
yourLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
yourLinearLayout.setLayoutManager(yearLayoutManager);
The most difficult problem is how can you get the middle Item of recyclerView, well I think you can learn more in this link:
https://github.com/plattysoft/SnappingList
Finally, to make the middle Item bigger than the others. In the RecyclerView's Adapter should be like this
public class DateAdapter extends RecyclerView.Adapter<DateAdapter.DateViewHolder> {
private ArrayList<LabelerDate> dateDataList;
private static final int VIEW_TYPE_PADDING = 1;
private static final int VIEW_TYPE_ITEM = 2;
private int selectedItem = -1;
public DateAdapter(ArrayList<Datasource> data, int paddingWidthDate) {
this.dateDataList = data;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//todo
}
#Override
public void onBindViewHolder(DateViewHolder holder, int position) {
Datasource data = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
holder.tvDate.setText(String.valueOf(data.valueDate));
holder.tvDate.setVisibility(View.VISIBLE);
holder.imgSmall.setVisibility(View.VISIBLE);
if (position == selectedItem) {
holder.tvDate.setTextColor(Color.parseColor("#094673"));
holder.tvDate.setTextSize(35);
holder.imgSmall.setBackgroundResource(R.color.textviewbold);
} else {
holder.tvDate.setTextColor(Color.GRAY);
holder.tvDate.setTextSize(35);
holder.imgSmall.setBackgroundResource(R.color.gray);
}
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
//todo
}
public class DateViewHolder extends RecyclerView.ViewHolder {
//todo
}}
After created The recyclerView, adapter and datasource. You can look at the onBindViewHolder(). If Item is selected then it's become bigger and change to black. If is not selected it will become Gray. My layout look like yours
use this library
Polidea view library
Put your TextView into a LinearLayout that lives on a ZoomView.
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.polidea.ZoomView
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:id="#+id/myLinearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</com.polidea.ZoomView>
</ScrollView>
The Polidea custom View didn't work in my case, as is. Had to a add a constructor with AttributeSet parameter, because it threw an xml inflation error. Also I had to make the ScrollView wrap only the TextView.
I'm using RecyclerView with GridLayoutManager. The goal I want to achieve is when I click on an item, It'll scale up and overlaps the adjacent items. Just like the picture below (in Android TV)
When the onClick event is triggered, I call
v.animate().scaleX(1.2f).scaleY(1.2f).setDuration(500).start();
But the result is below:
It can overlaps only items that has position lower than itself.
What should I do to overlaps all of the adjacent items. Thanks in advance.
EDIT
I already tried:
v.bringToFront();
or
(v.getParent()).bringChildToFront(v);
But both of them don't work.
According aga's answer, i use ViewCompat.setElevation(View view, float elevation) to support API prior to 21, It works like a charming.
static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View root) {
// bind views
// ...
// bind focus listener
root.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
// run scale animation and make it bigger
ViewCompat.setElevation(root, 1);
} else {
// run scale animation and make it smaller
ViewCompat.setElevation(root, 0);
}
}
});
}
}
The item layout file is very simple like below:
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:background="#drawable/sl_live_item" />
1.Override the getChildDrawingOrder method.
#Override
protected int getChildDrawingOrder(int childCount, int i) {
View view = getLayoutManager().getFocusedChild();
if (null == view) {
return super.getChildDrawingOrder(childCount, i);
}
int position = indexOfChild(view);
if (position < 0) {
return super.getChildDrawingOrder(childCount, i);
}
if (i == childCount - 1) {
return position;
}
if (i == position) {
return childCount - 1;
}
return super.getChildDrawingOrder(childCount, i);
}
2.RecyclerView setChildrenDrawingOrderEnabled is true.
setChildrenDrawingOrderEnabled(true);
3.When item view has focus, invalidate its parent.
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
v.setScaleX(1.5f);
v.setScaleY(1.5f);
mRecyclerView.invalidate();
} else {
v.setScaleX(1.0f);
v.setScaleY(1.0f);
}
}
Try to invoke v.bringToFront() before running the animation.
If it doesn't work another variant is to use View#setElevation(float val) where val > 0. The drawback is that you'll need to set elevation back to 0 when you're deselecting the item.
val zoom = resources.getFraction(...) // Your zoom fraction in %
override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
val viewPropertyAnimator: ViewPropertyAnimator
val f: Float
if(gainFocus) {
tvAppName.visibility = VISIBLE
f = this.zoom;
viewPropertyAnimator = animate().z(2.0f).scaleX(this.zoom);
} else {
tvAppName.visibility = INVISIBLE
ivAppIcon.strokeWidth = 0f
f = 1.0f;
viewPropertyAnimator = animate().z(0.0f).scaleX(1.0f);
}
viewPropertyAnimator.scaleY(f).duration = 150;
}
You can add this sample code to any layout to achieve desired effect.
You need to do it the old way you do with normal ViewGroups, overriding the draw order of children. This example forces the first view to always be drawn last - you need to customize for your use case.
public TopForcingRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setChildrenDrawingOrderEnabled(true);
}
#Override protected int getChildDrawingOrder(int childCount, int i) {
if (i >= childCount - 1) return 0;
//when asked for the last view to draw, answer ZERO
else return i + 1;
//when asked for the i-th view to draw, answer "the next one"
}
Pay attention to off by one errors.