diff --git a/README.md b/README.md index 7ede4df5..4c693241 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,22 @@ # CalenderView +[![](https://jitpack.io/v/michaellee123/CalendarView.svg)](https://jitpack.io/#michaellee123/CalendarView) + +李某人套娃基于[CalenderView](https://github.com/angcyo/CalendarView)`3.7.1.37`的版本修改,实现了滚动年月标题吸顶。如图: + +![](/png/gif_sticky_vertical_scroll.gif) + +吸顶效果使用:[GroupedRecyclerViewAdapter](https://github.com/donkingliang/GroupedRecyclerViewAdapter),感谢🙏 + +使用方法,用`StickyVerticalCalendarView`替换掉原本的`VerticalCalendarView`即可。 + +--- + +分割线 + +--- + 基于[CalenderView](https://github.com/huanghaibin-dev/CalendarView)`3.7.1`的版本修改, 实现了如下功能: - `垂直列表日历`: 基于`RecyclerView`实现 diff --git a/app/src/main/java/com/haibin/calendarviewproject/VerticalActivity.java b/app/src/main/java/com/haibin/calendarviewproject/VerticalActivity.java index d7233c71..c4147caf 100644 --- a/app/src/main/java/com/haibin/calendarviewproject/VerticalActivity.java +++ b/app/src/main/java/com/haibin/calendarviewproject/VerticalActivity.java @@ -10,6 +10,7 @@ import com.haibin.calendarview.BaseMonthView; import com.haibin.calendarview.Calendar; import com.haibin.calendarview.CalendarView; +import com.haibin.calendarview.StickyVerticalCalendarView; import com.haibin.calendarview.VerticalCalendarView; import com.haibin.calendarview.VerticalMonthRecyclerView; import com.haibin.calendarviewproject.base.activity.BaseActivity; @@ -32,7 +33,7 @@ public class VerticalActivity extends BaseActivity implements TextView mTextCurrentDay; - VerticalCalendarView mCalendarView; + StickyVerticalCalendarView mCalendarView; RelativeLayout mRelativeTool; int doCount = 0; diff --git a/app/src/main/res/layout/activity_vertical.xml b/app/src/main/res/layout/activity_vertical.xml index da972828..9f07b143 100644 --- a/app/src/main/res/layout/activity_vertical.xml +++ b/app/src/main/res/layout/activity_vertical.xml @@ -72,7 +72,7 @@ android:layout_gravity="center" android:layout_marginTop="2dp" android:gravity="center" - android:padding="8dp" + android:padding="16dp" android:text="切" android:textColor="#000000" android:textSize="12sp" /> @@ -84,7 +84,7 @@ android:layout_gravity="center" android:layout_marginTop="2dp" android:gravity="center" - android:padding="8dp" + android:padding="16dp" android:text="上" android:textColor="#000000" android:textSize="12sp" /> @@ -96,15 +96,15 @@ android:layout_gravity="center" android:layout_marginTop="2dp" android:gravity="center" - android:padding="8dp" + android:padding="16dp" android:text="下" android:textColor="#000000" android:textSize="12sp" /> @@ -115,7 +115,7 @@ android:contentDescription="@string/app_name" android:scaleType="centerInside" android:src="@mipmap/ic_calendar" - android:tint="#000000" /> + app:tint="#000000" /> - mSchemeDates) { + super.setSchemeDate(mSchemeDates); + monthRecyclerView.update(); + } + + @Override + public void clearSchemeDate() { + super.clearSchemeDate(); + monthRecyclerView.update(); + } + + @Override + public void setRange(int minYear, int minYearMonth, int minYearDay, int maxYear, int maxYearMonth, int maxYearDay) { + super.setRange(minYear, minYearMonth, minYearDay, maxYear, maxYearMonth, maxYearDay); + monthRecyclerView.updateRange(); + } + + @Override + protected void showSelectLayout(int year) { + super.showSelectLayout(year); + + monthRecyclerView.animate() + .scaleX(0) + .scaleY(0) + .setDuration(260) + .setInterpolator(new LinearInterpolator()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (mDelegate.mYearViewChangeListener != null) { + mDelegate.mYearViewChangeListener.onYearViewChange(false); + } + } + }); + } + + @Override + protected void closeSelectLayout(int position) { + super.closeSelectLayout(position); + + monthRecyclerView.setCurrentItem(position, false); + + monthRecyclerView.animate() + .scaleX(1) + .scaleY(1) + .setDuration(180) + .setInterpolator(new LinearInterpolator()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (mDelegate.mYearViewChangeListener != null) { + mDelegate.mYearViewChangeListener.onYearViewChange(true); + } + if (mParentLayout != null) { + mParentLayout.showContentView(); + if (mParentLayout.isExpand()) { + mMonthPager.setVisibility(VISIBLE); + } else { + mWeekPager.setVisibility(VISIBLE); + mParentLayout.shrink(); + } + } else { + mMonthPager.setVisibility(VISIBLE); + } + mMonthPager.clearAnimation(); + } + }); + } + + @Override + public void scrollToCurrent(boolean smoothScroll) { + super.scrollToCurrent(smoothScroll); + monthRecyclerView.scrollToCurrent(smoothScroll); + } + + @Override + public void scrollToNext(boolean smoothScroll) { + super.scrollToNext(smoothScroll); + monthRecyclerView.scrollToNext(smoothScroll); + } + + @Override + public void scrollToPre(boolean smoothScroll) { + super.scrollToPre(smoothScroll); + monthRecyclerView.scrollToPre(smoothScroll); + } + + @Override + public void scrollToCalendar(int year, int month, int day, boolean smoothScroll, boolean invokeListener) { + super.scrollToCalendar(year, month, day, smoothScroll, invokeListener); + monthRecyclerView.scrollToCalendar(year, month, day, smoothScroll, invokeListener); + } + + @Override + public void setMonthView(Class cls) { + super.setMonthView(cls); + monthRecyclerView.updateMonthViewClass(); + } +} + diff --git a/calendarview/src/main/java/com/haibin/calendarview/StickyVerticalMonthRecyclerView.java b/calendarview/src/main/java/com/haibin/calendarview/StickyVerticalMonthRecyclerView.java new file mode 100644 index 00000000..47748093 --- /dev/null +++ b/calendarview/src/main/java/com/haibin/calendarview/StickyVerticalMonthRecyclerView.java @@ -0,0 +1,309 @@ +package com.haibin.calendarview; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.donkingliang.groupedadapter.adapter.GroupedRecyclerViewAdapter; +import com.donkingliang.groupedadapter.holder.BaseViewHolder; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +/** + * 年月title吸顶的垂直日历 + * + * @author michaellee + * 2022/05/11 + */ +public class StickyVerticalMonthRecyclerView extends RecyclerView { + + protected CalendarViewDelegate mDelegate; + protected int mMonthCount; + protected CalendarLayout mParentLayout; + + public StickyVerticalMonthRecyclerView(@NonNull Context context) { + super(context); + init(context); + } + + public StickyVerticalMonthRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public StickyVerticalMonthRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + protected void init(@NonNull Context context) { + setLayoutManager(new LinearLayoutManager(context)); + + if (isInEditMode()) { + setup(new CalendarViewDelegate(context, null)); + } + } + + GroupVerticalMonthAdapter adapter; + + /** + * 初始化 + * + * @param delegate delegate + */ + void setup(CalendarViewDelegate delegate) { + this.mDelegate = delegate; + + mMonthCount = 12 * (mDelegate.getMaxYear() - mDelegate.getMinYear()) + - mDelegate.getMinYearMonth() + 1 + + mDelegate.getMaxYearMonth(); + if (adapter == null) { + adapter = new GroupVerticalMonthAdapter(getContext()); + } + setAdapter(adapter); + } + + void updateRange() { + if (getVisibility() != VISIBLE) { + return; + } + if (mDelegate != null) { + setup(mDelegate); + } + updateSelected(); + } + + void update() { + if (adapter != null) { + adapter.notifyItemRangeChanged(0, adapter.getGroupCount()); + } + } + + class MonthHolder { + BaseViewHolder viewHolder; + BaseMonthView monthView; + + public MonthHolder(BaseViewHolder holder) { + viewHolder = holder; + if (holder.itemView.getTag(R.id.tag_group_month_view) != null) { + monthView = (BaseMonthView) holder.itemView.getTag(R.id.tag_group_month_view); + } + } + } + + /** + * 更新选中的日期效果 + */ + void updateSelected() { + for (MonthHolder viewHolder : allViewHolder()) { + viewHolder.monthView.setSelectedCalendar(mDelegate.mSelectedCalendar); + viewHolder.monthView.invalidate(); + } + } + + /** + * 获取界面上的ViewHolder + */ + MonthHolder getViewHolder(int childIndex) { + if (childIndex >= 0 && childIndex < adapter.getGroupCount()) { + View child = getChildAt(childIndex * 2 + 1); + ViewHolder viewHolder = getChildViewHolder(child); + return new MonthHolder((BaseViewHolder) viewHolder); + } + return null; + } + + /** + * 界面上所有的ViewHolder + */ + List allViewHolder() { + ArrayList result = new ArrayList<>(); + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + ViewHolder holder = getChildViewHolder(child); + MonthHolder monthHolder = new MonthHolder((BaseViewHolder) holder); + if (monthHolder.monthView != null) { + result.add(monthHolder); + } + } + return result; + } + + public void setCurrentItem(int position) { + setCurrentItem(position, true); + } + + public void setCurrentItem(int pos, final boolean smoothScroll) { + final int position = pos * 2; + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager == null) { + return; + } + if (smoothScroll) { + ViewHolder holder = findViewHolderForAdapterPosition(position); + if (holder == null) { + smoothScrollToPosition(position); + } else { + int dy = layoutManager.getDecoratedTop(holder.itemView) - getPaddingTop(); + smoothScrollBy(0, dy); + } + } else { + if (layoutManager instanceof LinearLayoutManager) { + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0); + } else { + scrollToPosition(position); + } + } + updateSelected(); + } + + public void scrollToNext(boolean smoothScroll) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); + int first = linearLayoutManager.findFirstVisibleItemPosition(); + int groupPosition = adapter.getGroupPositionForPosition(first); + setCurrentItem(groupPosition + 1, smoothScroll); + } + + public void scrollToPre(boolean smoothScroll) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); + int first = linearLayoutManager.findFirstVisibleItemPosition(); + int groupPosition = adapter.getGroupPositionForPosition(first); + setCurrentItem(groupPosition - ((first & 1) == 1 ? 0 : 1), smoothScroll); + } + + public void scrollToCurrent(boolean smoothScroll) { + int position = 12 * (mDelegate.getCurrentDay().getYear() - mDelegate.getMinYear()) + + mDelegate.getCurrentDay().getMonth() - mDelegate.getMinYearMonth(); + setCurrentItem(position, smoothScroll); + } + + public void scrollToCalendar(int year, int month, int day, boolean smoothScroll, boolean invokeListener) { + Calendar calendar = new Calendar(); + calendar.setYear(year); + calendar.setMonth(month); + calendar.setDay(day); + calendar.setCurrentDay(calendar.equals(mDelegate.getCurrentDay())); + LunarCalendar.setupLunarCalendar(calendar); + mDelegate.mIndexCalendar = calendar; + mDelegate.mSelectedCalendar = calendar; + mDelegate.updateSelectCalendarScheme(); + int y = calendar.getYear() - mDelegate.getMinYear(); + int position = 12 * y + calendar.getMonth() - mDelegate.getMinYearMonth(); + setCurrentItem(position, smoothScroll); + } + + /** + * 更新月视图Class + */ + void updateMonthViewClass() { + setup(mDelegate); + setCurrentItem(mDelegate.mCurrentMonthViewItem, false); + } + + protected class GroupVerticalMonthAdapter extends GroupedRecyclerViewAdapter { + + public GroupVerticalMonthAdapter(Context context) { + super(context); + } + + @Override + public int getGroupCount() { + return mMonthCount; + } + + @Override + public int getChildrenCount(int groupPosition) { + return 1; + } + + @Override + public boolean hasHeader(int groupPosition) { + return true; + } + + @Override + public boolean hasFooter(int groupPosition) { + return false; + } + + @Override + public int getHeaderLayout(int viewType) { + return R.layout.cv_layout_vertical_month_sticky_title; + } + + @Override + public int getFooterLayout(int viewType) { + return 0; + } + + @Override + public int getChildLayout(int viewType) { + return R.layout.cv_layout_vertical_month_sticky_view; + } + + @Override + public void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition) { + + int year = (groupPosition + mDelegate.getMinYearMonth() - 1) / 12 + mDelegate.getMinYear(); + int month = (groupPosition + mDelegate.getMinYearMonth() - 1) % 12 + 1; + + TextView currentMonthView = holder.itemView.findViewById(R.id.current_month_view); + if (currentMonthView != null) { + currentMonthView.setText(year + "年" + month + "月"); + } + } + + @Override + public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) { + + } + + @Override + public void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition) { + + int year = (groupPosition + mDelegate.getMinYearMonth() - 1) / 12 + mDelegate.getMinYear(); + int month = (groupPosition + mDelegate.getMinYearMonth() - 1) % 12 + 1; + BaseMonthView monthView; + if (holder.itemView.getTag(R.id.tag_group_month_view) == null) { + try { + Constructor constructor = mDelegate.getMonthViewClass().getConstructor(Context.class); + monthView = (BaseMonthView) constructor.newInstance(getContext()); + + ViewGroup monthContainer = holder.itemView.findViewById(R.id.month_container); + monthContainer.addView(monthView); + } catch (Exception e) { + e.printStackTrace(); + monthView = new DefaultMonthView(getContext()); + } + + CalendarView.OnClassInitializeListener listener = mDelegate.mClassInitializeListener; + if (listener != null) { + listener.onClassInitialize(mDelegate.getMonthViewClass(), monthView); + } + holder.itemView.setTag(R.id.tag_group_month_view, monthView); + } else { + monthView = (BaseMonthView) holder.itemView.getTag(R.id.tag_group_month_view); + } + monthView.mParentLayout = mParentLayout; + monthView.setup(mDelegate); + monthView.setTag(groupPosition); + monthView.initMonthWithDate(year, month); + monthView.setSelectedCalendar(mDelegate.mSelectedCalendar); + + CalendarView.OnStickyVerticalItemInitializeListener stickyVerticalItemInitializeListener = mDelegate.mStickyVerticalItemInitializeListener; + if (stickyVerticalItemInitializeListener != null) { + stickyVerticalItemInitializeListener.onVerticalItemInitialize(holder, groupPosition, year, month); + } + } + } + +} diff --git a/calendarview/src/main/res/layout/cv_layout_vertical_calendar_sticky_view.xml b/calendarview/src/main/res/layout/cv_layout_vertical_calendar_sticky_view.xml new file mode 100644 index 00000000..7c309f91 --- /dev/null +++ b/calendarview/src/main/res/layout/cv_layout_vertical_calendar_sticky_view.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/calendarview/src/main/res/layout/cv_layout_vertical_month_sticky_title.xml b/calendarview/src/main/res/layout/cv_layout_vertical_month_sticky_title.xml new file mode 100644 index 00000000..c6f5c551 --- /dev/null +++ b/calendarview/src/main/res/layout/cv_layout_vertical_month_sticky_title.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/calendarview/src/main/res/layout/cv_layout_vertical_month_sticky_view.xml b/calendarview/src/main/res/layout/cv_layout_vertical_month_sticky_view.xml new file mode 100644 index 00000000..17d77899 --- /dev/null +++ b/calendarview/src/main/res/layout/cv_layout_vertical_month_sticky_view.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/calendarview/src/main/res/values/ids.xml b/calendarview/src/main/res/values/ids.xml new file mode 100644 index 00000000..7c44382e --- /dev/null +++ b/calendarview/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/png/gif_sticky_vertical_scroll.gif b/png/gif_sticky_vertical_scroll.gif new file mode 100644 index 00000000..a4c4dc60 Binary files /dev/null and b/png/gif_sticky_vertical_scroll.gif differ