When expanding and collapsing my Recyclerview items, divider lines drawn multiple times or overdrawn with items.
Also when expanding and collapsing views, dividers thickness is getting reduced.
My problem is divider line drawn each and every time I expand and collapse item in Recyclerview.
So is it possible to prevent divider line drawing if it already drawn?
While expanding an item, divider line will move according to the view.?
Below is my RecyclerView Decoration Class used for divider line,
public class SeparatorDecoration extends RecyclerView.ItemDecoration {
private final Paint mPaint;
/**
* Create a decoration that draws a line in the given color and width between the items in the view.
* #param context a context to access the resources.
* #param color the color of the separator to draw.
* #param heightDp the height of the separator in dp.
*/
public SeparatorDecoration(#NonNull Context context, #ColorInt int color,
#FloatRange(from = 0, fromInclusive = false) float heightDp) {
mPaint = new Paint();
mPaint.setColor(color);
final float thickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
heightDp, context.getResources().getDisplayMetrics());
mPaint.setStrokeWidth(thickness);
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// we want to retrieve the position in the list
final int position = params.getViewAdapterPosition();
// and add a separator to any view but the last one
if (position <state.getItemCount()) {
outRect.set(40, 0, 40, (int) mPaint.getStrokeWidth()); // left, top, right, bottom
} else {
outRect.setEmpty(); // 0, 0, 0, 0
}
}
#Override
public void onDrawOver(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
final int offset = (int) (mPaint.getStrokeWidth() / 2);
// this will iterate over every visible view
for (int i = 0; i < parent.getChildCount(); i++) {
// get the view
final View view = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// get the position
final int position = params.getViewAdapterPosition();
// and finally draw the separator
if (position < parent.getChildCount()) {
final int ty = (int)(view.getTranslationY() + 0.5f);
final int top = view.getBottom() - params.bottomMargin + ty;
final int bottom = top + (int) mPaint.getStrokeWidth();
c.drawLine(view.getLeft(), view.getBottom() + offset, view.getRight(), view.getBottom() + offset, mPaint);
}
}
}
}
below is my RecyclerView Adapter class,
public class DisplayNotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private Context context;
private List<NotificationDetails> notificationRecords;
private DeleteNotificationListener deleteNotificationListener;
private String TAG = DisplayNotificationAdapter.class.getSimpleName();
interface DeleteNotificationListener {
void updateNotificationList(List<NotificationDetails> details);
}
public DisplayNotificationAdapter(Context context, DeleteNotificationListener listener, List < NotificationDetails > notificationRecordsList) {
this.context = context;
this.deleteNotificationListener = listener;
this.notificationRecords = notificationRecordsList;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder (#NonNull ViewGroup parent, int viewType){
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
AndroidLogger.log(5,TAG,"oncreate");
View listItem = layoutInflater.inflate(R.layout.display_notification_recycler_view_list_item, parent, false);
return new NotificationViewHolder(listItem);
}
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onBindViewHolder (#NonNull RecyclerView.ViewHolder holder,int position){
NotificationDetails notification = notificationRecords.get(position);
NotificationViewHolder viewHolder = (NotificationViewHolder) holder;
String currentDateString = DateFormat.getDateInstance().format(Long.parseLong(notification.getTimeStamp()));
String filePath=generateFilePath(notification.getFileName());
Bitmap myBitmap = BitmapFactory.decodeFile(filePath);
#SuppressLint("SimpleDateFormat")
DateFormat dateFormat = new SimpleDateFormat("hh:mm aa");
String time = dateFormat.format(Long.parseLong(notification.getTimeStamp()));
if (notification.isExpanded()) {
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Up Arrow")));
expandView(viewHolder.notificationImageview);
}
else {
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Down Arrow")));
collapseView(viewHolder.notificationImageview);
}
viewHolder.notificationImageview.setImageBitmap(myBitmap);
viewHolder.notificationTextView.setText(notification.getMessage());
viewHolder.notificationTimeTextView.setText(time);
Calendar now = Calendar.getInstance();
Calendar date = Calendar.getInstance();
date.setTimeInMillis(Long.parseLong(notification.getTimeStamp()));
viewHolder.expandCollapseImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(notification.isExpanded()) {
notification.setExpanded(false);
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Down Arrow")));
viewHolder.notificationTextView.setMaxLines(1);
notifyItemChanged(position);
}
else {
notification.setExpanded(true);
viewHolder.expandCollapseImageView.setImageDrawable(context.getDrawable(ImageDrawable.getDrawable("Up Arrow")));
viewHolder.notificationTextView.setMaxLines(Integer.MAX_VALUE);
notifyItemChanged(position);
}
}
});
if (now.get(Calendar.DATE) == date.get(Calendar.DATE))
viewHolder.notificationDateTextView.setText("Today");
else if (now.get(Calendar.DATE) - date.get(Calendar.DATE) == 1)
viewHolder.notificationDateTextView.setText("Yesterday");
else
viewHolder.notificationDateTextView.setText(currentDateString);
if(notification.getTitle()==null)
viewHolder.notificationTitleTextView.setText("title");
else
viewHolder.notificationTitleTextView.setText(notification.getTitle());
}
private String generateFilePath(String fileName) {
File imageFileDirectory = context.getDir("image", Context.MODE_PRIVATE); //Creating an internal dir;
if (!imageFileDirectory.exists()) {
imageFileDirectory.mkdirs();
}
/*
* app server provide "U" file name after we set read status they provide same file name as "R"
*/
String createFilePath = imageFileDirectory + "/" + fileName;
return createFilePath;
}
public void removeSingleNotification ( int position){
DatabaseHelper databaseHelper = new DatabaseHelper(context);
databaseHelper.deleteSingleNotificationRecord(notificationRecords.get(position).getId());
notificationRecords.remove(position);
deleteNotificationListener.updateNotificationList(notificationRecords);
notifyDataSetChanged();
}
private void removeFromList (String id) {
for (NotificationDetails detail : notificationRecords) {
if (detail.getId().equalsIgnoreCase(id))
notificationRecords.remove(detail);
}
}
#Override
public int getItemCount () {
return notificationRecords.size();
}
public void expandView(final View v) {
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = v.getMeasuredHeight();
// Older versions of android (pre API 21) cancel animations for views with a height of 0.
v.getLayoutParams().height = 1;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int)(targetHeight * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// Expansion speed of 1dp/ms
a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public void collapseView(final View v) {
//collapse(pos);
final int initialHeight = v.getMeasuredHeight();
Animation a = new Animation()
{
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if(interpolatedTime == 1){
v.setVisibility(View.GONE);
}else{
v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
v.requestLayout();
}
}
#Override
public boolean willChangeBounds() {
return true;
}
};
// Collapse speed of 1dp/ms
a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
public static class NotificationViewHolder extends RecyclerView.ViewHolder {
private TextView notificationTextView, notificationDateTextView, notificationTimeTextView, notificationTitleTextView;
private ImageView notificationImageview,expandCollapseImageView;
private ConstraintLayout parent;
public NotificationViewHolder(#NonNull View itemView) {
super(itemView);
notificationTextView = itemView.findViewById(R.id.notification_text_view);
notificationDateTextView = itemView.findViewById(R.id.notification_date_text_view);
notificationTimeTextView = itemView.findViewById(R.id.notification_time_text_view);
notificationTitleTextView = itemView.findViewById(R.id.notification_title_text_view);
notificationImageview = itemView.findViewById(R.id.notification_image_view);
expandCollapseImageView = itemView.findViewById(R.id.expand_collapse_arrow);
parent = itemView.findViewById(R.id.notification_parent);
}
}
}
UPDATE
I doesn't able to solve this issue. So instead of using RecyclerView.ItemDecoration I have used a View inside layout like below,
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:id="#+id/view_div"
android:background="#color/grey"
Doing like above solves the issue.
[This is my screen contains the issue][1]][1]
https://i.stack.imgur.com/CBY80.jpg
Related
I have RecyclerView which show images as i want
RecyclerView detailsRecycleImage = (RecyclerView) view.findViewById(R.id.detailsRcycleImage);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(
getActivity().getApplicationContext(),
LinearLayoutManager.HORIZONTAL,
false
);
detailsRecycleImage.setLayoutManager(mLayoutManager);
ImagesAdapter imgAdapter = new
ImagesAdapter(getActivity(),contactsData.getContactImages());
detailsRecycleImage.setAdapter(imgAdapter);
And my adapter code is
public class ImagesAdapter extends RecyclerView.Adapter<ImagesAdapter.MyViewHolder>{
private Context cnt;
public ArrayList<String> imgsUrls;
public ImagesAdapter(Context cnt, ArrayList<String> imgsUrls) {
this.cnt=cnt;
this.imgsUrls=imgsUrls;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_img,parent,false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
String singleImg = imgsUrls.get(position);
Picasso.with(cnt).load(singleImg).into(holder.img);
}
#Override
public int getItemCount() {
return imgsUrls.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public ImageView img;
public MyViewHolder(View view) {
super(view);
img = (ImageView) view.findViewById(R.id.img);
}
}
}
And my layout is
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/img"
/>
</RelativeLay
I just need to add dots under the recyclerview as an indicator as this image
Is there any way to add something to my code and get this view ?
I slightly change LinePagerIndicatorDecoration to make it DotsIndicatorDecoration and to support both GridLayoutManager and LinearLayoutManager
public class DotsIndicatorDecoration extends RecyclerView.ItemDecoration {
private final int indicatorHeight;
private final int indicatorItemPadding;
private final int radius;
private final Paint inactivePaint = new Paint();
private final Paint activePaint = new Paint();
public DotsIndicatorDecoration(int radius, int padding, int indicatorHeight, #ColorInt int colorInactive, #ColorInt int colorActive) {
float strokeWidth = Resources.getSystem().getDisplayMetrics().density * 1;
this.radius = radius;
inactivePaint.setStrokeCap(Paint.Cap.ROUND);
inactivePaint.setStrokeWidth(strokeWidth);
inactivePaint.setStyle(Paint.Style.STROKE);
inactivePaint.setAntiAlias(true);
inactivePaint.setColor(colorInactive);
activePaint.setStrokeCap(Paint.Cap.ROUND);
activePaint.setStrokeWidth(strokeWidth);
activePaint.setStyle(Paint.Style.FILL);
activePaint.setAntiAlias(true);
activePaint.setColor(colorActive);
this.indicatorItemPadding = padding;
this.indicatorHeight = indicatorHeight;
}
#Override
public void onDrawOver(#NotNull Canvas c, #NotNull RecyclerView parent, #NotNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
final RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
// center horizontally, calculate width and subtract half from center
float totalLength = this.radius * 2 * itemCount;
float paddingBetweenItems = Math.max(0, itemCount - 1) * indicatorItemPadding;
float indicatorTotalWidth = totalLength + paddingBetweenItems;
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2f;
// center vertically in the allotted space
float indicatorPosY = parent.getHeight() - indicatorHeight / 2f;
drawInactiveDots(c, indicatorStartX, indicatorPosY, itemCount);
final int activePosition;
if (parent.getLayoutManager() instanceof GridLayoutManager) {
activePosition = ((GridLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
} else if (parent.getLayoutManager() instanceof LinearLayoutManager) {
activePosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
} else {
// not supported layout manager
return;
}
if (activePosition == RecyclerView.NO_POSITION) {
return;
}
// find offset of active page if the user is scrolling
final View activeChild = parent.getLayoutManager().findViewByPosition(activePosition);
if (activeChild == null) {
return;
}
drawActiveDot(c, indicatorStartX, indicatorPosY, activePosition);
}
private void drawInactiveDots(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
// width of item indicator including padding
final float itemWidth = this.radius * 2 + indicatorItemPadding;
float start = indicatorStartX + radius;
for (int i = 0; i < itemCount; i++) {
c.drawCircle(start, indicatorPosY, radius, inactivePaint);
start += itemWidth;
}
}
private void drawActiveDot(Canvas c, float indicatorStartX, float indicatorPosY,
int highlightPosition) {
// width of item indicator including padding
final float itemWidth = this.radius * 2 + indicatorItemPadding;
float highlightStart = indicatorStartX + radius + itemWidth * highlightPosition;
c.drawCircle(highlightStart, indicatorPosY, radius, activePaint);
}
#Override
public void getItemOffsets(#NotNull Rect outRect, #NotNull View view, #NotNull RecyclerView parent, #NotNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = indicatorHeight;
}
}
Usage:
RecyclerView recyclerView = resourceLayout.getSuccessView().findViewById(R.id.cardsRecyclerView);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(manageCardGenericAdapter);
final int radius = getResources().getDimensionPixelSize(R.dimen.radius);
final int dotsHeight = getResources().getDimensionPixelSize(R.dimen.dots_height);
final int color = ContextCompat.getColor(getContext(), R.color.primaryBlue);
recyclerView.addItemDecoration(new DotsIndicatorDecoration(radius, radius * 4, dotsHeight, color, color));
new PagerSnapHelper().attachToRecyclerView(recyclerView);
Check this out:
https://blog.davidmedenjak.com/android/2017/06/24/viewpager-recyclerview.html
Tldr:
Create a LinePagerIndicatorDecoration and add it to our RecyclerView:
// pager indicator
recyclerView.addItemDecoration(new LinePagerIndicatorDecoration());
I've tried decisions with decorator - strange behaviour: my horizontal recyclerview does not stick to edges of screen. check out my decision -
recycler.setAdapter(recyclerViewHorizontalAdapter);
recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
int currentCompletelyVisibleLab = ((LinearLayoutManager)recyclerViewLayoutManager).findFirstCompletelyVisibleItemPosition();
bottomDotsTransaction(currentCompletelyVisibleLab);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
});
addBottomDots();
setLabsOnTouchListener(commercialLabsNames, availableTests);
}
//create empty linear layout below recyclerview and add dots to it
private void addBottomDots() {
ImageView imageView;
for (int i = 0; i < commercialLabsLogos.size(); i++) {
imageView = new ImageView(getContext());
imageView.setImageResource(R.drawable.empty_dot_4dp);
imageView.setPadding(15, 15, 15, 15);
dotsLinearLayout.addView(imageView);
}
bottomDotsTransaction(0);
}
private void bottomDotsTransaction(int pos) {
for (int i = 0; i < dotsLinearLayout.getChildCount(); i++) {
if (dotsLinearLayout.getChildAt(i) instanceof ImageView) {
((ImageView) dotsLinearLayout.getChildAt(i)).setImageResource(R.drawable.empty_dot_4dp);
}
}
//Set the chosen dot on position
((ImageView) dotsLinearLayout.getChildAt(pos)).setImageResource(R.drawable.fill_dot_6dp);
}
you just need to set OnScrollListener and inside its callback re-define your linearlayout with dots via your layout_manager and methods
.findFirstCompletelyVisibleItemPosition()
or
.findLastVisibleItemPosition()
.findLastCompletelyVisibleItemPosition()
This is what I want:
As image above, I want to draw a center line on RecycleView, then get the center item when scrolling (as well as move left or right)
Here is my try to draw a horizontal RecycleView:
HorizontalAdapter adapter = new HorizontalAdapter(data);
LinearLayoutManager layoutManager
= new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recycleView.setLayoutManager(layoutManager);
recycleView.setAdapter(adapter);
Is there any way to know which item is moved to the center of RecycleView? And how can I scroll RecycleView to left or right just one position?
Update: I tried to use a scroll listener to get the middle position, but it doesn't work as an aspect.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstPos = layoutManager.findFirstVisibleItemPosition();
int lastPos = layoutManager.findLastVisibleItemPosition();
int middle = Math.abs(lastPos - firstPos) / 2 + firstPos;
int selectedPos = -1;
for (int i = 0; i < adapter.getItemCount(); i++) {
if (i == middle) {
adapter.getItem(i).setSelected(true);
selectedPos = i;
} else {
adapter.getItem(i).setSelected(false);
}
}
adapter.notifyDataSetChanged();
}
And get the result:
I only want to change the selected item (make text to white color) when it is on the blue Rect
I made something just like this. I can do exactly what you need.
First of all, this is how is my alogrithm work
This is my recyclerView Adapter
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 paddingWidthDate = 0;
private int selectedItem = -1;
public DateAdapter(ArrayList<LabelerDate> dateData, int paddingWidthDate) {
this.dateDataList = dateData;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_date,
parent, false);
return new DateViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_padding,
parent, false);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.width = paddingWidthDate;
view.setLayoutParams(layoutParams);
return new DateViewHolder(view);
}
}
#Override
public void onBindViewHolder(DateViewHolder holder, int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
if(labelerDate.dateType.equals(BirthDayActivity.DateType.C31))
holder.tvDate.setText(String.valueOf(labelerDate.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 getItemCount() {
return dateDataList.size();
}
#Override
public int getItemViewType(int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (labelerDate.dateType.equals(BirthDayActivity.DateType.NONE)) {
return VIEW_TYPE_PADDING;
}
return VIEW_TYPE_ITEM;
}
public class DateViewHolder extends RecyclerView.ViewHolder {
public TextView tvDate;
public ImageView imgSmall;
public DateViewHolder(View itemView) {
super(itemView);
tvDate = (TextView) itemView.findViewById(R.id.tvNumberDate);
imgSmall = (ImageView) itemView.findViewById(R.id.small_marked_dob);
}
}}
This is most important alogrithm:
public void getRecyclerviewDate() {
recyclerViewDate = (RecyclerView) findViewById(R.id.recyclerViewDay);
ViewTreeObserver vtoDate = recyclerViewDate.getViewTreeObserver();
vtoDate.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerViewDate.getViewTreeObserver().removeOnPreDrawListener(this);
finalWidthDate = recyclerViewDate.getMeasuredWidth();
itemWidthDate = getResources().getDimension(R.dimen.item_dob_width);
paddingDate = (finalWidthDate - itemWidthDate) / 2;
firstItemWidthDate = paddingDate ;
allPixelsDate = 0;
final LinearLayoutManager dateLayoutManager = new LinearLayoutManager(getApplicationContext());
dateLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerViewDate.setLayoutManager(dateLayoutManager);
recyclerViewDate.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
synchronized (this) {
if(newState == RecyclerView.SCROLL_STATE_IDLE){
calculatePositionAndScrollDate(recyclerView);
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
allPixelsDate += dx;
}
});
if (labelerDates == null)
labelerDates = new ArrayList<>();
labelerDates.addAll(genLabelerDate(currentMonth, currentYear));
dateAdapter = new DateAdapter(labelerDates, (int) firstItemWidthDate);
recyclerViewDate.setAdapter(dateAdapter);
return true;
}
});
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void calculatePositionAndScrollDate(RecyclerView recyclerView) {
int expectedPositionDate = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
if (expectedPositionDate == -1) {
expectedPositionDate = 0;
} else if (expectedPositionDate >= recyclerView.getAdapter().getItemCount() - 2) {
expectedPositionDate--;
}
scrollListToPositionDate(recyclerView, expectedPositionDate);
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void scrollListToPositionDate(RecyclerView recyclerView, int expectedPositionDate) {
float targetScrollPosDate = expectedPositionDate * itemWidthDate + firstItemWidthDate - paddingDate;
float missingPxDate = targetScrollPosDate - allPixelsDate;
if (missingPxDate != 0) {
recyclerView.smoothScrollBy((int) missingPxDate, 0);
}
}
private void setDateValue() {
int expectedPositionDateColor = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
setColorDate = expectedPositionDateColor + 1;
//set color here
dateAdapter.setSelecteditem(setColorDate);
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
allPixelsDate = savedInstanceState.getFloat(BUNDLE_LIST_PIXELS_DATE);
allPixelsDateChanged = savedInstanceState.getFloat(BUNDLE_LIST_PIXELS_DATE_CHANGED);
}
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putFloat(BUNDLE_LIST_PIXELS_DATE, allPixelsDate);
outState.putFloat(BUNDLE_LIST_PIXELS_DATE_CHANGED, allPixelsDateChanged);
}
And this is my result:
Look at this video link, this is my app demo
Sometimes is needed the entire example code block together, because we may miss something. Here is what I have, feel free to correct anything since I may be doing some little mistake somewhere. And Yes, this answer is an extension of #tranhieu answer. Thanks #tranhieu.
MainActivity.java
package com.test;
import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import java.util.ArrayList;
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
public float firstItemWidthDate;
public float paddingDate;
public float itemWidthDate;
public int allPixelsDate;
public int finalWidthDate;
private DateAdapter dateAdapter;
private ArrayList<LabelerDate> labelerDates = new ArrayList<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getRecyclerviewDate();
}
public void getRecyclerviewDate() {
final RecyclerView recyclerViewDate = (RecyclerView) findViewById(R.id.rv_tasks_date);
if (recyclerViewDate != null) {
recyclerViewDate.postDelayed(new Runnable() {
#Override
public void run() {
setDateValue();
}
}, 300);
recyclerViewDate.postDelayed(new Runnable() {
#Override
public void run() {
recyclerViewDate.smoothScrollToPosition(dateAdapter.getItemCount()-1);
setDateValue();
}
}, 5000);
}
ViewTreeObserver vtoDate = recyclerViewDate.getViewTreeObserver();
vtoDate.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerViewDate.getViewTreeObserver().removeOnPreDrawListener(this);
finalWidthDate = recyclerViewDate.getMeasuredWidth();
itemWidthDate = getResources().getDimension(R.dimen.item_dob_width);
paddingDate = (finalWidthDate - itemWidthDate) / 2;
firstItemWidthDate = paddingDate;
allPixelsDate = 0;
final LinearLayoutManager dateLayoutManager = new LinearLayoutManager(getApplicationContext());
dateLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerViewDate.setLayoutManager(dateLayoutManager);
recyclerViewDate.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
synchronized (this) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
calculatePositionAndScrollDate(recyclerView);
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
allPixelsDate += dx;
}
});
if (labelerDates == null) {
labelerDates = new ArrayList<>();
}
genLabelerDate();
dateAdapter = new DateAdapter(labelerDates, (int) firstItemWidthDate);
recyclerViewDate.setAdapter(dateAdapter);
dateAdapter.setSelecteditem(dateAdapter.getItemCount() - 1);
return true;
}
});
}
private void genLabelerDate() {
for (int i = 0; i < 32; i++) {
LabelerDate labelerDate = new LabelerDate();
labelerDate.setNumber(Integer.toString(i));
labelerDates.add(labelerDate);
if (i == 0 || i == 31) {
labelerDate.setType(DateAdapter.VIEW_TYPE_PADDING);
} else {
labelerDate.setType(DateAdapter.VIEW_TYPE_ITEM);
}
}
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void calculatePositionAndScrollDate(RecyclerView recyclerView) {
int expectedPositionDate = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
if (expectedPositionDate == -1) {
expectedPositionDate = 0;
} else if (expectedPositionDate >= recyclerView.getAdapter().getItemCount() - 2) {
expectedPositionDate--;
}
scrollListToPositionDate(recyclerView, expectedPositionDate);
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void scrollListToPositionDate(RecyclerView recyclerView, int expectedPositionDate) {
float targetScrollPosDate = expectedPositionDate * itemWidthDate + firstItemWidthDate - paddingDate;
float missingPxDate = targetScrollPosDate - allPixelsDate;
if (missingPxDate != 0) {
recyclerView.smoothScrollBy((int) missingPxDate, 0);
}
setDateValue();
}
//
private void setDateValue() {
int expectedPositionDateColor = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
int setColorDate = expectedPositionDateColor + 1;
// set color here
dateAdapter.setSelecteditem(setColorDate);
}
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 paddingWidthDate = 0;
private int selectedItem = -1;
public DateAdapter(ArrayList<LabelerDate> dateData, int paddingWidthDate) {
this.dateDataList = dateData;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
return new DateViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.width = paddingWidthDate;
view.setLayoutParams(layoutParams);
return new DateViewHolder(view);
}
}
#Override
public void onBindViewHolder(DateViewHolder holder, int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
holder.tvDate.setText(labelerDate.getNumber());
holder.tvDate.setVisibility(View.VISIBLE);
Log.d(TAG, "default " + position + ", selected " + selectedItem);
if (position == selectedItem) {
Log.d(TAG, "center" + position);
holder.tvDate.setTextColor(Color.parseColor("#76FF03"));
holder.tvDate.setTextSize(35);
} else {
holder.tvDate.setTextColor(Color.WHITE);
holder.tvDate.setTextSize(18);
}
} else {
holder.tvDate.setVisibility(View.INVISIBLE);
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return dateDataList.size();
}
#Override
public int getItemViewType(int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (labelerDate.getType() == VIEW_TYPE_PADDING) {
return VIEW_TYPE_PADDING;
} else {
return VIEW_TYPE_ITEM;
}
}
public class DateViewHolder extends RecyclerView.ViewHolder {
public TextView tvDate;
public DateViewHolder(View itemView) {
super(itemView);
tvDate = (TextView) itemView.findViewById(R.id.txt_date);
}
}
}
private class LabelerDate {
private int type;
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_tasks_date"
android:layout_width="match_parent"
android:layout_height="48dp" />
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginTop="48dp"
android:src="#android:drawable/ic_dialog_info" />
</FrameLayout>
</LinearLayout>
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="#+id/txt_date"
android:layout_width="#dimen/item_dob_width"
android:layout_height="48dp"
android:text="32"
android:textColor="#android:color/white"
android:background="#android:color/darker_gray"
android:textSize="28sp"
android:gravity="center"/>
</LinearLayout>
dimens.xml
<resources>
<dimen name="item_dob_width">100dp</dimen>
</resources>
Oh boy. I've been searching for this answer for almost a week and then found out the solution. Custom LayoutManagers? No. ItemDecorator? Nope.
Here's the easiest way to do it:
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingStart="150dp"
android:paddingEnd="150dp"
android:clipToPadding="false" />
The critical part is:
android:paddingStart="150dp"
android:paddingEnd="150dp"
android:clipToPadding="false"
And then just assign SnapHelper to your RecylcerView:
val snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
This is it. The easiest and most perfect solution to the problem
I'm used the SnapHelper right here:
// init snaphelper
SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView)
// init layout manager
LinearLayoutManager layoutManager = new LinearLayoutManager(mainActivity);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
// init adapter
adatper.setSnapHelper(snapHelper);
adatper.setLayoutManager(layoutManager);
adatper.initAdapter(new Float((DisplayHelper.getDisplayWidth(mainActivity) / 2) - (fooViewWidth / 2)).intValue());
recyclerView.setAdapter(adatper);
As said by TranHieu the solution of inserting 2 item for padding (at start and at end positions) is good.
I don't like the use of ViewTreeObserver because of poor readability of code. With this technique then you must also manage redrawing of the items if they are recycled.
If you are using customview classes you can set its width directly into these classes.
For example this is my padding class
/**
* Created by firegloves on 25/09/15.
*/
#EViewGroup(R.layout.view_padding)
public class PaddingView extends FooView {
Context mCtx;
public PaddingView(Context context) {
super(context);
mCtx = context;
}
public void setWidth(int width) {
setLayoutParams(new LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
In my adapter I store the desired padding item width, that is equal to (displayWidth / 2) - (realItemWidth / 2)
This is my adapter, don't look at methods not matching RecyclerView.Adapter, pay attention to the initAdapter method and to the onCreateItemView method
#EBean
public class FooAdapterRecycler extends RecyclerViewAdapterBase<Foo, FooView> {
private final int TYPE_PADDING_VIEW = 0;
private final int TYPE_REAL_VIEW = 1;
#RootContext
Context ctx;
#Bean(Finder.class)
IFinder finder;
SnapHelper snapHelper;
RecyclerView.LayoutManager layoutManager;
private int paddingWidth = 0;
/**
* preleva i dati dal finder
*/
public void initAdapter(int paddingWidth) {
/*******************************
* THIS CODE IS THE IMPORTANT ONE
******************************/
this.paddingWidth = paddingWidth;
// add 1 item for initial space
mItems = new ArrayList<>();
Foo foo = new Foo();
mItems.add(foo);
// get real items from finder
mItems.addAll(finder.findAll());
// add 1 item for final space
mItems = new ArrayList<>();
Foo foo2 = new Foo();
mItems.add(foo2);
}
#Override
public int getItemViewType(int position) {
if (position == 0 || position == getItemCount()-1) {
return TYPE_PADDING_VIEW;
} else {
return TYPE_REAL_VIEW;
}
}
#Override
protected FooView onCreateItemView(ViewGroup parent, int viewType) {
/*******************************
* THIS CODE IS THE IMPORTANT ONE
******************************/
if (viewType == TYPE_PADDING_VIEW) {
PaddingView view = PaddingView_.build(ctx);
view.setWidth(paddingWidth);
return view;
} else {
return FooView_.build(ctx);
}
}
public void setSnapHelper(SnapHelper snapHelper) {
this.snapHelper = snapHelper;
}
public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
}
I'm using AndroidAnnotations library but it's not required
Hope that helps
USING SNAPHELPER - A SMOOTHER SOLUTION
Here it is another solution using SnapHelper. Starting from the answer of #TranHieu here:
https://stackoverflow.com/a/34647005/3944251
and the compressed by #sector11 here:
https://stackoverflow.com/a/38411582/3944251
I wrote the following code which is also based in both answers above, but it's simpler and offers a smoother solution using SnapHelper presented in android support library 24.2.0.
Here you have the MainActivity class. The rest is the same with #sector11's answer.
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
public float firstItemWidthDate;
public float itemWidthDate;
public int allPixelsDate;
public int finalWidthDate;
private DateAdapter dateAdapter;
private ArrayList<LabelerDate> labelerDates;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
labelerDates = new ArrayList<>();
getRecyclerviewDate();
}
public void getRecyclerviewDate() {
final RecyclerView recyclerViewDate = (RecyclerView) findViewById(R.id.rv_tasks_date);
recyclerViewDate.postDelayed(new Runnable() {
#Override
public void run() {
//recyclerViewDate.smoothScrollToPosition(dateAdapter.getItemCount()-1);
setDateValue();
}
}, 300);
ViewTreeObserver vtoDate = recyclerViewDate.getViewTreeObserver();
vtoDate.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerViewDate.getViewTreeObserver().removeOnPreDrawListener(this);
finalWidthDate = recyclerViewDate.getMeasuredWidth();
itemWidthDate = getResources().getDimension(R.dimen.item_dob_width);
firstItemWidthDate = (finalWidthDate - itemWidthDate) / 2;
allPixelsDate = 0;
final LinearLayoutManager dateLayoutManager = new LinearLayoutManager(getApplicationContext());
dateLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerViewDate.setLayoutManager(dateLayoutManager);
/* Create a LinearSnapHelper and attach the recyclerView to it. */
final LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerViewDate);
recyclerViewDate.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
allPixelsDate += dx;
recyclerView.post(new Runnable() {
public void run() {
setDateValue();
}
});
}
});
genLabelerDate();
dateAdapter = new DateAdapter(labelerDates, (int) firstItemWidthDate);
recyclerViewDate.setAdapter(dateAdapter);
dateAdapter.setSelecteditem(dateAdapter.getItemCount() - 1);
return true;
}
});
}
private void genLabelerDate() {
for (int i = 0; i < 32; i++) {
LabelerDate labelerDate = new LabelerDate();
labelerDate.setNumber(Integer.toString(i));
labelerDates.add(labelerDate);
if (i == 0 || i == 31) {
labelerDate.setType(DateAdapter.VIEW_TYPE_PADDING);
} else {
labelerDate.setType(DateAdapter.VIEW_TYPE_ITEM);
}
}
}
//
private void setDateValue() {
int expectedPositionDateColor = Math.round(allPixelsDate / itemWidthDate);
int setColorDate = expectedPositionDateColor + 1;
// set color here
dateAdapter.setSelecteditem(setColorDate);
}
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 paddingWidthDate = 0;
private int selectedItem = -1;
public DateAdapter(ArrayList<LabelerDate> dateData, int paddingWidthDate) {
this.dateDataList = dateData;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateAdapter.DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
if (viewType == VIEW_TYPE_PADDING) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.width = paddingWidthDate;
view.setLayoutParams(layoutParams);
}
return new DateViewHolder(view);
}
#Override
public void onBindViewHolder(DateAdapter.DateViewHolder holder, int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
holder.tvDate.setText(labelerDate.getNumber());
holder.tvDate.setVisibility(View.VISIBLE);
Log.d(TAG, "default " + position + ", selected " + selectedItem);
if (position == selectedItem) {
Log.d(TAG, "center" + position);
holder.tvDate.setTextColor(Color.parseColor("#76FF03"));
holder.tvDate.setTextSize(35);
} else {
holder.tvDate.setTextColor(Color.WHITE);
holder.tvDate.setTextSize(18);
}
} else {
holder.tvDate.setVisibility(View.INVISIBLE);
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return dateDataList.size();
}
#Override
public int getItemViewType(int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (labelerDate.getType() == VIEW_TYPE_PADDING) {
return VIEW_TYPE_PADDING;
} else {
return VIEW_TYPE_ITEM;
}
}
public class DateViewHolder extends RecyclerView.ViewHolder {
public TextView tvDate;
public DateViewHolder(View itemView) {
super(itemView);
tvDate = (TextView) itemView.findViewById(R.id.txt_date);
}
}
}
private class LabelerDate {
private int type;
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
}
You can use a LinearSnapHelper
Attach to your recyclerView like
val snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(this)
Then, to get the center view, use
snapHelper.findSnapView(horizontalScrollView.layoutManager)?
As mentioned in the other answer, there is no direct way to do this.
This is probably how you can achieve what you described in the question.
Know the number of items visible on the screen.
Select the middle item programmatically every time the view is scrolled.
Keep a partially transparent image as an overlay on the middle item on the recyclerview. (You'll need to compute the coordinates based on the width of the recycler view or width of the screen and the width of the overlay image you choose to put.
Refresh the selected value in a text view below the recycler view every time there is a scroll.
The image overlays have to be placed in a way they appear connected and as one single control.
For this feature use EcoGallery library:
https://github.com/falnatsheh/EcoGallery
At first, I needed something similar, not this. But I was able to adapt #TranHieu solution to my needs, so I voted up his solution.
I wanted to create full-screen horizontal recyclerview that after user sroll updates scrollPosition to mostVisibleItem.
setup:
private void setUpScrolling() {
mRecyclerVIew.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mRecyclerVIew.getViewTreeObserver().removeOnPreDrawListener(this);
CustomScrollListener listener = (CustomScrollListener) mScrollListener;
listener.width = mRecyclerVIew.getMeasuredWidth();
listener.dx = 0;
return true;
}
});
}
listener:
private class CustomScrollListener extends OnScrollListener {
private int mLastDx = 0;
int width = 0;
int dx = 0;
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (mLastDx != dx) {
scrollToMostVisibleItem();
} else {
dx = 0;
mLastDx = 0;
}
}
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.dx += dx;
}
private void scrollToMostVisibleItem() {
int direction = (dx > 0) ? 1 : -1;
dx = Math.abs(dx);
int shiftCount = Math.round(dx / width);
int pixelShift = dx % width;
if (pixelShift > width / 2) {
shiftCount++;
}
float targetScrollPixels = shiftCount * width;
float finalScrollPixels = (targetScrollPixels - dx) * direction;
if (finalScrollPixels != 0) {
mRecyclerVIew.smoothScrollBy((int) finalScrollPixels, 0);
mLastDx = (int) finalScrollPixels;
dx = 0;
}
}
}
I used another approach in my case.
you can find the deatils here: RecyclerView - How highlight central visible item during scroll1
In my opinion, my solution is more easy than the others.
If someone is looking for a more generic implementation, here is my code based on the answers of this thread:
Add the CenterLinearSnapHelper
public class CenterLinearSnapHelper extends LinearSnapHelper {
//Constants
public static final String TAG = CenterLinearSnapHelper.class.getSimpleName();
//Attributes
private Context context;
private float itemWidth;
private OnPaddingComputationListener listener;
//Constructors
/**
* A linear snap helper which helps centering the items in a recyclerview.
*
* #param itemWidth The (fixed) width of a child view in pixels.
*/
public CenterLinearSnapHelper(float itemWidth) {
this.itemWidth = itemWidth;
}
public void attachToRecyclerView(#Nullable RecyclerView recyclerView,
#NonNull OnPaddingComputationListener listener) throws IllegalStateException {
this.listener = listener;
//Calculates the padding for the first and end item
calculatePadding(recyclerView);
//Create a LinearSnapHelper and attach the recyclerView to it.
attachToRecyclerView(recyclerView);
}
public float getItemWidth() {
return itemWidth;
}
private void calculatePadding(RecyclerView recyclerView) {
if (recyclerView == null)
return;
ViewTreeObserver observer = recyclerView.getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
int finalWidth = recyclerView.getMeasuredWidth();
float padding = (finalWidth - itemWidth) / 2;
listener.onPadding(padding, finalWidth);
return true;
}
});
}
public interface OnPaddingComputationListener {
void onPadding(float padding, int finalWidth);
}
}
In your Activity/Fragment where you create your RecyclerView:
float itemWidth = getResources().getDimension(R.dimen.favorite_room_width);
CenterLinearSnapHelper snapHelper = new CenterLinearSnapHelper(itemWidth);
snapHelper.attachToRecyclerView(binding.listFavorites, (padding, finalWidth) -> {
//Set the adapter
roomAdapter = new RoomAdapter(requireContext(), rooms);
roomAdapter.addPaddingItems((int) padding);
roomAdapter.setOnToggleClickListener(FavoritesFragment.this);
binding.listFavorites.setAdapter(roomAdapter);
});
In your adapter:
public void addPaddingItems(int padding) {
if (padding < 0)
throw new IllegalStateException("Padding cannot be smaller than 0");
this.padding = padding;
//Add 2 new items as the first and last
//NOTE: If you update your existing dataset (e.g add new items), you should redo the calculation!
rooms.add(0, new Room("First"));
rooms.add(rooms.size(), new Room("Last"));
}
#Override
public int getItemViewType(int position) {
if (padding >= 0 && (position == 0 || position == rooms.size() - 1)) {
return VIEW_TYPE_PADDING;
}
return VIEW_TYPE_ITEM;
}
#NonNull
#Override
public RoomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ViewFavoriteRoomBinding binding = DataBindingUtil.inflate(inflater, R.layout.view_favorite_room, parent, false);
if (viewType == VIEW_TYPE_PADDING) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) binding.getRoot().getLayoutParams();
layoutParams.width = padding;
binding.getRoot().setLayoutParams(layoutParams);
}
RoomViewHolder viewHolder = new RoomViewHolder(context, binding, onToggleClickListener);
viewHolder.getRecyclerView().setRecycledViewPool(viewPool);
return viewHolder;
}
In my scenario if I click on a expanded card from card_1 then all other cards should be invisible. Also, can I change the positions of the card runtime so it can move 2nd card on the position of the first and it will expand to full screen so there will be no need to hide other cards.
Please check the attached image.
public class DashboardMarketAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int VIEW_TYPE_TOP_MENU = 0;
private static final int VIEW_TYPE_MARKET_MOVERS = 1;
private static final int VIEW_TYPE_DERIVATIVES = 2;
private static final int VIEW_TYPE_NEWS = 3;
private final Context context;
private final String[] menuItems;
public DashboardMarketAdapter(Context context, String[] menuItems) {
super();
this.context = context;
this.menuItems = menuItems;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
switch (viewType) {
case VIEW_TYPE_TOP_MENU:
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_top_view, parent, false);
return new TopMenuViewHolder(v, parent.getContext());
case VIEW_TYPE_MARKET_MOVERS:
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_market_movers, parent, false);
return new MarketMoversViewHolder(v, parent.getContext());
case VIEW_TYPE_DERIVATIVES:
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_market_derivatives, parent, false);
return new MarketDerivativesViewHolder(v);
case VIEW_TYPE_NEWS:
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_top_news, parent, false);
return new TopNewsViewHolder(v);
default:
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
/**
* The length of the string array will tell recycler view the count of the card views.
*/
#Override
public int getItemCount() {
return menuItems.length;
}
/**
* #param position this will return the view of the card according to the position of the card.
*/
#Override
public int getItemViewType(int position) {
switch (position) {
case VIEW_TYPE_TOP_MENU:
return VIEW_TYPE_TOP_MENU;
case VIEW_TYPE_MARKET_MOVERS:
return VIEW_TYPE_MARKET_MOVERS;
case VIEW_TYPE_DERIVATIVES:
return VIEW_TYPE_DERIVATIVES;
case VIEW_TYPE_NEWS:
return VIEW_TYPE_NEWS;
default:
return 0;
}
}
/**
* This view holder is holding the view of topmost card,
* which contains tab and viewpager.
*/
static class TopMenuViewHolder extends RecyclerView.ViewHolder {
#InjectView(R.id.pager)
ViewPager top_view_pager;
#InjectView(R.id.tabs)
DashboardSlidingTabLayout top_view_tabs;
#InjectView(R.id.expand)
Button btnExpand;
#InjectView(R.id.card_view)
CardView cardView;
private DashboardTabAdapter adapter;
private CharSequence Titles[];
private int NumberOfTabs;
int pos;
static int setView = 0;
int minHeight;
public static final int TOP_VIEW = 0;
public static final int INDICES = 1;
public static final int COMMODITY = 2;
public static final int CURRENCY = 3;
public TopMenuViewHolder(View itemView, final Context context) {
super(itemView);
ButterKnife.inject(this, itemView);
Titles = context.getResources().getStringArray(R.array.string_array_dashboard_tab_menu);
Bundle bundle = new Bundle();
bundle.putInt("setView", setView);
NumberOfTabs = Titles.length;
adapter = new DashboardTabAdapter(((FragmentActivity) context).getSupportFragmentManager(), Titles, NumberOfTabs, bundle);
top_view_pager.setAdapter(adapter);
top_view_tabs.setViewPager(top_view_pager);
/**
* Here visibility of Expand button is decided upon the tab selected.
*/
top_view_pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
pos = position;
if (position > 0) {
btnExpand.setVisibility(View.VISIBLE);
initializeTab();
} else if (position == 0) {
btnExpand.setVisibility(View.GONE);
collapseView();
}
}
});
/**
* This will be used to give initial height for the top card.
*/
cardView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
cardView.getViewTreeObserver().removeOnPreDrawListener(this);
minHeight = cardView.getHeight();
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
layoutParams.height = minHeight;
cardView.setLayoutParams(layoutParams);
return true;
}
});
/**
* Here you can get the height of the screen
*/
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dimension = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dimension);
final int height = dimension.heightPixels;
/**
* Here you can add logic for expanding a card.
*/
btnExpand.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i("Dashboard", "Current Fragment" + top_view_pager.getCurrentItem());
new ChildRecyclerView(context, true);
new ParentRecyclerView(context, false);
toggleCardViewnHeight(height);
}
});
}
/**
* This method will toggle expand and collapse of card view.
*/
private void toggleCardViewnHeight(int height) {
int ViewMinHeight = minHeight;
if (cardView.getHeight() == ViewMinHeight) {
// expand
expandView(height);
} else {
// collapse
collapseView();
}
}
/**
* This method will initialize tab adapter depending upon the page selected.
*/
public void initializeTab() {
switch (top_view_pager.getCurrentItem()) {
case INDICES:
((IndicesFragment) adapter.getRegisteredFragment(INDICES)).initUI();
break;
case COMMODITY:
((CommodityDashboardFragment) adapter.getRegisteredFragment(COMMODITY)).initUI();
break;
case CURRENCY:
((CurrencyDashboardFragment) adapter.getRegisteredFragment(CURRENCY)).initUI();
break;
}
}
/**
* Collapse View Animation
*/
public void collapseView() {
BaseFragment.isExpanded = 0;
ValueAnimator anim = ValueAnimator.ofInt(cardView.getMeasuredHeightAndState(),
minHeight);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
layoutParams.height = val;
cardView.setLayoutParams(layoutParams);
}
});
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
initializeTab();
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
}
/**
* Expand View Animation
*/
public void expandView(int height) {
BaseFragment.isExpanded = 1;
ValueAnimator anim = ValueAnimator.ofInt(cardView.getMeasuredHeightAndState(),
height);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
layoutParams.height = val;
cardView.setLayoutParams(layoutParams);
}
});
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
initializeTab();
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
}
}
/**
* This view holder is holding the view of MarketMovers card,
*/
static class MarketMoversViewHolder extends RecyclerView.ViewHolder {
#InjectView(R.id.btnExpand)
Button btnExpand;
#InjectView(R.id.card_view)
CardView cardView;
#InjectView(R.id.relativeLayout)
RelativeLayout relativeLayout;
int height;
String []menuItem={"MARKET MOVERS","DERIVATIVES","TOP NEWS"};
public MarketMoversViewHolder(View itemView, final Context context) {
super(itemView);
ButterKnife.inject(this, itemView);
relativeLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
relativeLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
height = relativeLayout.getHeight();
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
layoutParams.height = 400;
cardView.setLayoutParams(layoutParams);
}
});
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dimension = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dimension);
int width = dimension.widthPixels;
final int screenHeight = dimension.heightPixels;
final int fullHeight = screenHeight - 400;
btnExpand.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
toggleCardViewnHeight(fullHeight);
DashboardMarketAdapter DM =new DashboardMarketAdapter(context,menuItem);
DM.notifyDataSetChanged();
}
});
}
private void toggleCardViewnHeight(int height) {
int ViewMinHeight = 400;
if (cardView.getHeight() == ViewMinHeight) {
// expand
ValueAnimator anim = ValueAnimator.ofInt(cardView.getMeasuredHeightAndState(),
height);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
layoutParams.height = val;
cardView.setLayoutParams(layoutParams);
}
});
anim.start();
} else {
// collapse
ValueAnimator anim = ValueAnimator.ofInt(cardView.getMeasuredHeightAndState(),
ViewMinHeight);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int val = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
layoutParams.height = val;
cardView.setLayoutParams(layoutParams);
}
});
anim.start();
}
}
}
/**
* This view holder is holding the view of Derivatives card,
*/
private class MarketDerivativesViewHolder extends RecyclerView.ViewHolder {
public MarketDerivativesViewHolder(View v) {
super(v);
}
}
/**
* This view holder is holding the view of NEWS card,
*/
private class TopNewsViewHolder extends RecyclerView.ViewHolder {
public TopNewsViewHolder(View v) {
super(v);
}
}
}
I have a project where I use a horizontal recycler view and I want to center one element. My implementation works, but not in every case check this GIF here:
As you may note it scrolls correctly if I come from the left. If I come from the right it overscrolls a lot and I have no idea how to stop nor how to fix that.
I striped my code to this example here:
public class DemoActivity extends ActionBarActivity implements View.OnClickListener {
private static final int JUMP_TO_LEFT = MyAdapter.NON_VISIBLE_ITEMS + MyAdapter.VISIBLE_ITEMS - 1;
private static final int JUMP_TO_RIGHT = MyAdapter.NON_VISIBLE_ITEMS;
private LinearLayoutManager mLayoutManager;
private RecyclerView mRecycler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
findViewById(android.R.id.button1).setOnClickListener(this);
mRecycler = (RecyclerView)findViewById(R.id.recycler);
MyAdapter mAdapter = new MyAdapter();
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setHasFixedSize(true);
mRecycler.scrollToPosition(MyAdapter.NON_VISIBLE_ITEMS);
mRecycler.setAdapter(mAdapter);
}
#Override
public void onClick(View v) {
int pos = mLayoutManager.findFirstVisibleItemPosition();
int outer = (MyAdapter.VISIBLE_ITEMS - 1) / 2;
if(pos + outer >= MyAdapter.ITEM_IN_CENTER) {
mRecycler.smoothScrollToPosition(JUMP_TO_RIGHT);
} else {
mRecycler.smoothScrollToPosition(JUMP_TO_LEFT);
}
}
}
And here is my adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.Holder> implements View.OnClickListener {
public static final int VISIBLE_ITEMS = 7;
public static final int NON_VISIBLE_ITEMS = 150;
private static final int TOTAL_ITEMS = VISIBLE_ITEMS + NON_VISIBLE_ITEMS * 2;
public static final int ITEM_IN_CENTER = (int)Math.ceil(VISIBLE_ITEMS / 2f) + NON_VISIBLE_ITEMS;
private Calendar mCalendar;
public MyAdapter() {
mCalendar = GregorianCalendar.getInstance();
setHasStableIds(true);
}
private int getToday() {
return (int)TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
}
#Override
public int getItemCount() {
return TOTAL_ITEMS;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
final TextView tv = new TextView(parent.getContext());
int width = parent.getWidth() / VISIBLE_ITEMS;
tv.setLayoutParams(new TableRow.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT, 1));
tv.setGravity(Gravity.CENTER);
tv.setBackgroundColor(Color.TRANSPARENT);
DisplayMetrics metrics = tv.getContext().getResources().getDisplayMetrics();
float padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);
tv.setLineSpacing(padding, 1f);
tv.setPadding(0, (int)padding, 0, 0);
tv.setOnClickListener(this);
return new Holder(tv);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
int today = getToday();
mCalendar.setTimeInMillis(System.currentTimeMillis());
mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems
mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1);
DateFormat format = new SimpleDateFormat("E\nd");
String label = format.format(mCalendar.getTime()).replace(".\n", "\n");
int day = (int)TimeUnit.MILLISECONDS.toDays(mCalendar.getTimeInMillis());
holder.update(day, today, label);
}
#Override
public long getItemId(int position) {
mCalendar.setTimeInMillis(System.currentTimeMillis());
mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems
mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1);
DateFormat format = new SimpleDateFormat("dMMyyyy");
return Long.parseLong(format.format(mCalendar.getTime()));
}
#Override
public void onClick(View v) {
String day = ((TextView)v).getText().toString().replace("\n", " ");
Toast.makeText(v.getContext(), "You clicked on " + day, Toast.LENGTH_SHORT).show();
}
public class Holder extends RecyclerView.ViewHolder {
private final Typeface font;
private Holder(TextView v) {
super(v);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
font = Typeface.create("sans-serif-light", Typeface.NORMAL);
} else {
font = null;
}
}
public void update(int day, int today, String label) {
TextView tv = (TextView)itemView;
tv.setText(label);
if(day == today) {
tv.setTextSize(18);
tv.setTypeface(null, Typeface.BOLD);
} else {
tv.setTextSize(16);
tv.setTypeface(font, Typeface.NORMAL);
}
tv.setBackgroundColor(0xff8dc380);
}
}
}
Do you see a reason for that? To make it simpler for you I also put this code on GitHub. https://github.com/rekire/RecylcerViewBug
I found a surprising simple workaround:
#Override
public void onClick(View v) {
int pos = mLayoutManager.findFirstVisibleItemPosition();
int outer = (MyAdapter.VISIBLE_ITEMS + 1) / 2;
int delta = pos + outer - ForecastAdapter.ITEM_IN_CENTER;
//Log.d("Scroll", "delta=" + delta);
View firstChild = mForecast.getChildAt(0);
if(firstChild != null) {
mForecast.smoothScrollBy(firstChild.getWidth() * -delta, 0);
}
}
Here I calculate the width to jump myself, that does exactly what I want.
In case of LinearLayoutManager with vertical orientation you can create own SmoothScroller and override calculateDyToMakeVisible() method where you can set desired view position. For example, to make the target view always to be appeared at the top side of RecyclerView after smoothScroll() write this:
class CustomLinearSmoothScroller extends LinearSmoothScroller {
public CustomLinearSmoothScroller(Context context) {
super(context);
}
#Override
public int calculateDyToMakeVisible(View view, int snapPreference) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (!layoutManager.canScrollVertically()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
final int viewHeight = bottom - top;
final int start = layoutManager.getPaddingTop();
final int end = start + viewHeight;
return calculateDtToFit(top, bottom, start, end, snapPreference);
}
"top" and "bottom" - bounds of the target view
"start" and "end" - points between which the view should be placed during smoothScroll
This worked for me:
itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0)
To support smooth scrolling, you must override
smoothScrollToPosition(RecyclerView, State, int) and create a
RecyclerView.SmoothScroller.
RecyclerView.LayoutManager is responsible
for creating the actual scroll action. If you want to provide a custom
smooth scroll logic, override smoothScrollToPosition(RecyclerView,
State, int) in your LayoutManager.
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#smoothScrollToPosition(int)
In your case, use smoothScrollBy could be a workaround (doesn't need this override).
I have listview similar to this:
Add code, it isn't much, but maybe helpful:
CustomAdapter:
public class MyAdapter extends BaseAdapter{
private LayoutInflater mInflater;
private Animation fadeIn;
private Animation fadeOut;
public MyAdapter(Activity c) {
mInflater = c.getLayoutInflater();
fadeIn = AnimationUtils.loadAnimation(c.getApplicationContext(), R.anim.alpha_show);
fadeIn.setFillAfter(true);
fadeOut = AnimationUtils.loadAnimation(c.getApplicationContext(), R.anim.alpha_dissappear);
fadeOut.setFillAfter(true);
}
public int getCount() {
return MainButtonsList.getList().getSize() +
GlobalPrefs.getEmptyRowsAtEnd() +
GlobalPrefs.getEmptyRowsAtStart();
}
public View getView(int position, View v, ViewGroup parent) {
View convertView = v;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.main_list_item, null);
}
ImageView iv = (ImageView) convertView.findViewById(R.id.image);
if ((position > GlobalPrefs.getEmptyRowsAtStart() - 1) && (position < getCount() - GlobalPrefs.getEmptyRowsAtEnd())) {
iv.setImageResource(MainButtonsList.getList().getListImageResource(position - GlobalPrefs.getEmptyRowsAtStart()));
iv.setAlpha(255);
iv.setTag(MainButtonsList.UNPRESSED_BUTTON_TAG);
} else {
iv.setTag(MainButtonsList.UNUSED_BUTTON_TAG);
iv.setAlpha(0);
iv.setVisibility(0);
iv.setClickable(false);
iv.setImageResource(R.drawable.logo_list_null);
}
iv.setMaxHeight(GlobalPrefs.getRowHeight());
iv.setMaxWidth(GlobalPrefs.getRowWidth());
iv.setBackgroundResource(0);
return convertView;
}
public Object getItem(int position) {
return MainButtonsList.getList().getObject(position);
}
public long getItemId(int position) {
return position;
}
}
Custom ListView:
public class CopyOfListView3d extends ListView{
private final Camera mCamera = new Camera();
private final Matrix mMatrix = new Matrix();
private Context context;
private Paint mPaint;
public CopyOfListView3d(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.setChildrenDrawingOrderEnabled(true);
}
#Override
protected int getChildDrawingOrder (int childCount, int i) {
//sets order number to each child, so makes overlap and center is always on top
}
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// get top left coordinates
boolean isCenter = false;
final int top = child.getTop();
final int bottom = child.getBottom();
Bitmap bitmap = child.getDrawingCache();
if (bitmap == null) {
child.setDrawingCacheEnabled(true);
child.buildDrawingCache();
bitmap = child.getDrawingCache();
}
final int centerY = child.getHeight() / 2;
final int centerX = child.getWidth() / 2;
final int radius = getHeight() / 2;
final int absParentCenterY = getTop() + getHeight() / 2;
final int absChildCenterY = child.getTop() + centerY;
final int distanceY = (absParentCenterY - absChildCenterY) / 2;
final int absDistance = Math.min(radius, Math.abs(distanceY));
final float translateZ = (float) Math.sqrt((radius * radius) - (absDistance * absDistance));
mCamera.save();
float myTranslateX = (float) (translateZ * (1.5f));
int density = GlobalPrefs.getDensity();
if (density < DisplayMetrics.DENSITY_LOW) {
myTranslateX = (float) myTranslateX - 80;
} else if (density == DisplayMetrics.DENSITY_LOW) {
myTranslateX = (float) myTranslateX - GlobalPrefs.getScreenWidth() + density + 40;
} else if (density <= DisplayMetrics.DENSITY_MEDIUM) {
myTranslateX = (float) myTranslateX - ((float)(GlobalPrefs.getScreenWidth()*0.75)) + density/2;
} else
if (density <= DisplayMetrics.DENSITY_HIGH) {
myTranslateX = (float) myTranslateX - 320;
} else
if (density > DisplayMetrics.DENSITY_HIGH) {
//Log.i("density", "this is more than high");
myTranslateX = (float) myTranslateX;
}
if ((top < absParentCenterY) && (bottom > absParentCenterY)) {
//make center row bigger
isCenter = true;
mCamera.translate((float) myTranslateX, 0, (float) -160);//130
child.setPressed(true);
child.setTag(MainButtonsList.PRESSED_BUTTON_TAG);
}
else {
//top
child.setTag(MainButtonsList.UNPRESSED_BUTTON_TAG);
child.setPressed(false);
mCamera.translate((float) myTranslateX, 0, -150);//120;
};
mCamera.getMatrix(mMatrix);
mCamera.restore();
// create and initialize the paint object
if (mPaint == null) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
}
//calculates alpha for each row, so far from center, rows are barely visible
mPaint.setAlpha(calculateAlpha(absChildCenterY));
mMatrix.postTranslate((float) (-centerX * 1.5f), top);
canvas.drawBitmap(bitmap, mMatrix, mPaint);
return false;
}
}
They are made of ImageViews where white color is selected state and blue color is unselected state and I change selected/unselected by screen coordinates, so center always is selected.
I have custom 3dListView which extends ListView and made custom adapter, where i set imageviews to rows. Code is too much to show, but there are pretty simple
One of these could be helpfull:
1) How can I make fade in (maybe also fade out) for center row (I can find which row is center when giving parent or childCount)? (Better)
2) It also could be helpfull if there is any OnStateChange listener, which listens to view's presses. Then I could fade in anytime when view becomes pressed and fadeout, when looses press.
I found an answer myself. Added this code piece to my custom ListView in drawChild method end:
final ImageView iv = (ImageView) child.findViewById(R.id.image);
child.setTag(MainButtonsList.PRESSED_BUTTON_TAG);
Animation anim = fadeOut();
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
public void onAnimationEnd(Animation animation) {
child.setPressed(true);
iv.startAnimation(fadeIn());
}
});
iv.clearAnimation();
iv.startAnimation(anim);
In list item layout, added another image, which put under item and now it looks prety nice.
You'll want to do the fading in the adapter for the listview.
In your adapter
private List<YourListObject> deleteItems;
// Constructor to init list and other vars
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Draw stuff
// including your object
for(YourListObject obj : deleteItems){
if(currentObj.equals(obj)){
Animation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f);
fadeOutAnimation.setDuration(300);
DeleteAnimationListener listener = new DeleteAnimationListener(currentObj);
fadeOutAnimation.setAnimationListener(listener);
someView.startAnimation(fadeOutAnimation);
}
}
return someView;
}
public void remove(YourListObject obj){
deleteItems.add(obj);
notifyDataSetChanged();
}
protected class DeleteAnimationListener extends EndAnimationListener {
private final YourListObject obj;
public DeleteAnimationListener(YourListObject obj) {
this.obj = obj;
}
#Override
public void onAnimationEnd(Animation animation) {
yourObjects.remove(obj);
deleteItems.remove(obj);
notifyDataSetChanged();
}
};
currentObj is the view in the list your currently manipulating
yourObjects is the list you are storing the data in.
deleteItems is a new list.
You call .remove(obj) on your adapter to remove one.
The getView will then remove it from the list (with a fade out animation) once this animation is finished the AnimationListener will remove the actual object from your dataset.