介绍
上个星期项目有一个日历价格的需求,类似一个商品在不同的日期价格可能会不同,由于时间给得特别紧所以打算找个合适的开源项目进行修改。参考了网上大多数是通过继承view直接draw一个monthView,然后通过listview来实现monthView的复用。但是继承view通过draw来实现月份日历比较麻烦,如果需要修改样式或者添加额外的信息会比较麻烦,所以为什么不用gridview来实现月份的显示呢?这样monthview的每个布局都是写在xml里的,别人参考你的改起来也方便,并且大多于自己的需求不符合,所以自己实现了一个价格日历,这里分享出来给大家参考。先贴一下效果图
实现思路
前面提到了用gridView来显示月份,要实现日历肯定有很多月份,所以我们用viewPager+gridView来实现,这里我们不仅要实现效果 还要以后遇到类似的功能只需要简单使用而不是重写,所以考虑自定义view中的组合类型。关于自定义view我之前有多篇文章讲到,概念性的东西就不提了,直接开始。
具体实现
组合控件其实就是将多个控件组合在一个控件里并且在该控件中实现一些交互和处理,是为了封装一些内部特性,方便直接使用。
步骤1 获取内部控件
。上面我们提到用viewPager+gridView来实现,当然还有一个显示月份的textview和两个button。那么第一步就是获取到这些控件。声明变量并在onFinishInflate() 方法中获取。
代码如下:
public class CommonCalendarView extends FrameLayout implements View.OnClickListener { private ViewPager mViewPager; private TextView mMonthTv; private Context mContext; private android.widget.ImageButton mLeftMonthBtn; private android.widget.ImageButton mRightMonthBtn; public CommonCalendarView(Context context) { this(context,null); } public CommonCalendarView(Context context, AttributeSet attrs) { this(context, attrs,0); } public CommonCalendarView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; } @Override protected void onFinishInflate() { super.onFinishInflate(); View view = LayoutInflater.from(mContext).inflate(R.layout.activity_page_calendar_price,this,true); this.mViewPager = (ViewPager) view.findViewById(R.id.viewPager); this.mRightMonthBtn = (ImageButton) view.findViewById(R.id.right_month_btn); this.mMonthTv = (TextView) view.findViewById(R.id.month_tv); this.mLeftMonthBtn = (ImageButton) view.findViewById(R.id.left_month_btn); this.mLeftMonthBtn.setOnClickListener(this); this.mRightMonthBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.left_month_btn: mViewPager.setCurrentItem(mViewPager.getCurrentItem()-1,true); break; case R.id.right_month_btn: mViewPager.setCurrentItem(mViewPager.getCurrentItem()+1,true); break; } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
代码大家肯定一看就知道,获取控件,为左右两个按钮设置点击实现。直接调用viewPager的setCurrentItem();
有了基本的控件以后那么我们还需要用来显示数据,从使用者的角度来说我可能只想告诉你最大最小日期,或者最大年份就可以了。这里提供一个接口获取最大年份并且声明最大最小日期变量,代码如下:
... private DatePickerController mController; private CalendarAdapter adapter; private Date maxDate; private Date minDate; public void setMaxDate(Date maxDate) { this.maxDate = maxDate; } public void setMinDate(Date minDate) { this.minDate = minDate; } public interface DatePickerController { int getMaxYear(); void onDayOfMonthSelected(int year, int month, int day); void onDayOfMonthAndDataSelected(int year,int month,int day,List obj); void showOtherFields(Object obj, View view, int gridItemYear, int gridItemMonth, int gridItemDay); Map<String,List> getDataSource(); }...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
为外部提供控件初始化方法用来获取数据
public void init(DatePickerController controller){ if (controller==null){ mController = new DatePickerController() { @Override public int getMaxYear() { return DateUtils.getToYear()+1; } @Override public void onDayOfMonthSelected(int year, int month, int day) { Toast.makeText(mContext, String.format("%s-%s-%s", year,StringUtils.leftPad(String.valueOf(month),2,"0"), StringUtils.leftPad(String.valueOf(day),2,"0")), Toast.LENGTH_SHORT).show(); } @Override public void onDayOfMonthAndDataSelected(int year, int month, int day, List obj) { } @Override public void showOtherFields(Object obj, View view, int gridItemYear, int gridItemMonth, int gridItemDay) { } @Override public Map<String, List> getDataSource() { return null; } }; }else{ mController = controller; } this.mYearMonthMap = mController.getDataSource(); adapter = new CalendarAdapter(mContext); mViewPager.setPageTransformer(true,new DepthPageTransformer()); mViewPager.setAdapter(adapter); if (minDate!=null){ mMonthTv.setText(String.format("%s年%s月",DateUtils.getYear(minDate), StringUtils.leftPad(String.valueOf(DateUtils.getMonth(minDate)),2,"0"))); }else{ mMonthTv.setText(String.format("%s年%s月",DateUtils.getToYear(), StringUtils.leftPad(String.valueOf(DateUtils.getToMonth()),2,"0"))); } mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { mMonthTv.setText(adapter.getPageTitle(position)); } @Override public void onPageScrollStateChanged(int state) { } }); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
上面的代码如果用户没有提供给我们,我们默认数据源提供maxYear 为今年+1,默认点击事件实现Toast提示。
否则的话直接为controller赋值。
同时设置viewPager的adapter。
来看adapter的代码
class CalendarAdapter extends PagerAdapter implements AdapterView.OnItemClickListener { protected static final int MONTHS_IN_YEAR = 12; private final Calendar calendar = Calendar.getInstance(); private Integer firstMonth = calendar.get(Calendar.MONTH); private LayoutInflater inflater; private Integer lastMonth = (calendar.get(Calendar.MONTH) - 1) % MONTHS_IN_YEAR; private Integer startYear = calendar.get(Calendar.YEAR); public CalendarAdapter(Context context) { inflater = LayoutInflater.from(context); mContext = context; if (maxDate!=null){ lastMonth = DateUtils.getMonth(maxDate)-1; } if (minDate!=null){ startYear = DateUtils.getYear(minDate); firstMonth = DateUtils.getMonth(minDate)-1; } } @Override public CharSequence getPageTitle(int position) { int year = position / MONTHS_IN_YEAR + startYear + ((firstMonth + (position % MONTHS_IN_YEAR)) / MONTHS_IN_YEAR); int month = (firstMonth + (position % MONTHS_IN_YEAR)) % MONTHS_IN_YEAR; return String.format("%s年%s月",year, StringUtils.leftPad(String.valueOf(month+1),2,"0")); } @Override public int getCount() { int maxYear = mController.getMaxYear(); int minYear = calendar.get(Calendar.YEAR) ; if (maxDate!=null){ maxYear = DateUtils.getYear(maxDate); } if (minDate!=null){ minYear = DateUtils.getYear(minDate); } int itemCount = (maxYear-minYear+1) * MONTHS_IN_YEAR; if (firstMonth != -1) itemCount -= firstMonth; if (lastMonth != -1) itemCount -= (MONTHS_IN_YEAR - lastMonth) - 1; return itemCount; } @Override public Object instantiateItem(ViewGroup container, int position) { GridView mGridView = mViewMap.get(position); if (mGridView ==null){ mGridView = (GridView) inflater.inflate(R.layout.item_page_month_day, container, false); mViewMap.put(position,mGridView); } int year = position / MONTHS_IN_YEAR + startYear + ((firstMonth + (position % MONTHS_IN_YEAR)) / MONTHS_IN_YEAR); int month = (firstMonth + (position % MONTHS_IN_YEAR)) % MONTHS_IN_YEAR; DateBean dateBean = new DateBean(year, month + 1); mGridView.setOnItemClickListener(this); mGridView.setAdapter(new MyGridAdapter(dateBean)); container.addView(mGridView); return mGridView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { MyGridAdapter gridAdapter = (MyGridAdapter) parent.getAdapter(); int day = (int) gridAdapter.getItem(position); if (day == -1) { return; } DateBean bean = gridAdapter.getDateBean(); List<ProductDatePrice> list = gridAdapter.getProductDatePriceList(); if (mController!=null){ if (list!=null&&!list.isEmpty()){ mController.onDayOfMonthAndDataSelected(bean.currentYear,bean.currentMonth,day+1,list); }else{ mController.onDayOfMonthSelected(bean.currentYear,bean.currentMonth,day+1); } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
通过getCount方法获取总count,确定要显示的总数。
在instantiateItem方法中根据position获取当前年月,然后为gridView设置adapter。
gridView代码如下:
class MyGridAdapter extends BaseAdapter { private DateBean mDateBean; private int days; private int dayOfWeeks; private List mProductDatePriceList; public DateBean getDateBean() { return mDateBean; } public MyGridAdapter(DateBean dateBean) { this.mDateBean = dateBean; if (mYearMonthMap!=null){ this.mProductDatePriceList = mYearMonthMap.get(String.format("%s-%s", dateBean.currentYear, StringUtils.leftPad(dateBean.currentMonth + "", 2, "0"))); } GregorianCalendar c = new GregorianCalendar(dateBean.currentYear, dateBean.currentMonth - 1, 0); days = DateUtils.getDaysOfMonth(dateBean.currentYear, dateBean.currentMonth); dayOfWeeks = c.get(Calendar.DAY_OF_WEEK); if (dayOfWeeks == 7) { dayOfWeeks = 0; } } public List getProductDatePriceList() { return mProductDatePriceList; } @Override public int getCount() { return days + dayOfWeeks; } @Override public Object getItem(int i) { if (i < dayOfWeeks) { return -1; } else { return i - dayOfWeeks; } } @Override public long getItemId(int i) { return 0; } @Override public View getView(int i, View view, ViewGroup viewGroup) { GridViewHolder viewHolder ; if (view == null) { view = LayoutInflater.from(mContext).inflate(R.layout.item_day, viewGroup, false); viewHolder = new GridViewHolder(); viewHolder.mTextView = (TextView) view.findViewById(R.id.day_tv); viewHolder.mPriceTv = (TextView) view.findViewById(R.id.price_tv); viewHolder.mLineView = view.findViewById(R.id.line_view); view.setTag(viewHolder); } else { viewHolder = (GridViewHolder) view.getTag(); } int item = (int) getItem(i); if (item == -1) { viewHolder.mTextView.setText(""); viewHolder.mPriceTv.setText(""); } else { viewHolder.mTextView.setText(String.valueOf(item + 1)); viewHolder.mPriceTv.setText(""); if (i%7==0||i%7==6){ viewHolder.mTextView.setActivated(true); }else{ viewHolder.mTextView.setActivated(false); } if (mProductDatePriceList != null) { viewHolder.mTextView.setEnabled(false); view.setEnabled(false); for (Object obj : mProductDatePriceList) { if (mController!=null){ mController.showOtherFields(obj,view,mDateBean.currentYear,mDateBean.currentMonth,item+1); } } } } return view; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
主要是根据当前年月 获取dayOfWeeks,本月第一天为星期几,然后判断需要空出几格。
使用
简单使用
1、xml中声明view
<com.qiangyu.test.commoncalendarview.view.CommonCalendarView android:id="@+id/calendarView" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.qiangyu.test.commoncalendarview.view.CommonCalendarView>
2、获取view并设置数据源
public class SimpleCalendarActivity extends AppCompatActivity { private CommonCalendarView calendarView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple_calendar); this.calendarView = (CommonCalendarView) findViewById(R.id.calendarView); this.calendarView.setMinDate(DateUtils.stringtoDate("1937-01-01","yyyy-MM-dd")); this.calendarView.setMaxDate(DateUtils.stringtoDate("2100-01-22","yyyy-MM-dd")); this.calendarView.init(null); }}
日历添加额外信息
1、xml中声明view
<com.qiangyu.test.commoncalendarview.view.CommonCalendarView android:id="@+id/calendarView" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.qiangyu.test.commoncalendarview.view.CommonCalendarView>
2、获取view并且设置数据源,
public class MoreInfoCalendarActivity extends AppCompatActivity { private CommonCalendarView calendarView; private Map<String,List> mYearMonthMap = new HashMap<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_more_info_calendar); List<ProductDatePrice> mDatePriceList = new ArrayList<>(); for (int i = 1; i <= 12; i++) { for (int j = 1; j <= 28; j++) { ProductDatePrice price = new ProductDatePrice(); price.setPriceDate(String.format("2017-%s-%s", StringUtils.leftPad(String.valueOf(i), 2, "0"), StringUtils.leftPad(String.valueOf(j), 2, "0"))); price.setPrice(RandomUtils.nextInt(1000)); mDatePriceList.add(price); } } for (ProductDatePrice productDatePrice : mDatePriceList) { productDatePrice.getPriceDate(); String yearMonth = TextUtils.substring(productDatePrice.getPriceDate(), 0, TextUtils.lastIndexOf(productDatePrice.getPriceDate(), '-')); List list = mYearMonthMap.get(yearMonth); if (list == null) { list = new ArrayList(); list.add(productDatePrice); mYearMonthMap.put(yearMonth, list); } else { list.add(productDatePrice); } } this.calendarView = (CommonCalendarView) findViewById(R.id.calendarView); this.calendarView.init(new CommonCalendarView.DatePickerController() { @Override public int getMaxYear() { return 2018; } @Override public void onDayOfMonthSelected(int year, int month, int day) { Toast.makeText(MoreInfoCalendarActivity.this, String.format("%s-%s-%s", year,StringUtils.leftPad(String.valueOf(month),2,"0"), StringUtils.leftPad(String.valueOf(day),2,"0")), Toast.LENGTH_SHORT).show(); } @Override public void onDayOfMonthAndDataSelected(int year, int month, int day, List obj) { if (obj==null){ return; } String priceDate = String.format("%s-%s-%s", year, StringUtils.leftPad(month + "", 2, "0"), StringUtils.leftPad(String.valueOf(day), 2, "0")); for (int i = 0; i < obj.size(); i++) { ProductDatePrice datePrice = (ProductDatePrice) obj.get(i); if (datePrice==null){ continue; } if (TextUtils.equals(datePrice.getPriceDate(),priceDate)){ Toast.makeText(MoreInfoCalendarActivity.this, datePrice.toString(), Toast.LENGTH_SHORT).show(); } } } @Override public void showOtherFields(Object obj, View view, int gridItemYear, int gridItemMonth, int gridItemDay) { ProductDatePrice productDatePrice = (ProductDatePrice) obj; if (TextUtils.equals(productDatePrice.getPriceDate(), String.format("%s-%s-%s", gridItemYear, StringUtils.leftPad(gridItemMonth + "", 2, "0"), StringUtils.leftPad(String.valueOf(gridItemDay), 2, "0")))) { CommonCalendarView.GridViewHolder viewHolder = (CommonCalendarView.GridViewHolder) view.getTag(); viewHolder.mPriceTv.setText(String.format("¥ %s", productDatePrice.getPrice())); view.setEnabled(true); viewHolder.mTextView.setEnabled(true); } } @Override public Map<String, List> getDataSource() { return mYearMonthMap; } }); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
结语
本偏的自定义价格日历控件还有很多不完善的地方,在这里分享出来只是抛砖引玉,希望对大家有所帮助,欢迎关注我的博客!
源码下载地址(github):
gitHub CalendarView
源码下载地址(csdn):
gitHub android自定义价格日历控件