Android自定义签到日历控件

   之前一直想写一个android的签到控件,参照了网上的博客,决定自己写一个比较全的。在此感谢

Android日历签到,超级简单的实现方式 带来的启发。

先来看一下实现效果:

整体的思路是这样的:

选择使用GridView作为日期的显示,但是需要注意的一点就是需要给GridView设置一个最大尺寸,这个我们可以通过继承GridView,重载里面的方法来进行实现。

代码如下:

public class SignGridView extends GridView {
    public SignGridView(Context context) {
        super(context);
    }
    public SignGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SignGridView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 3, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
}

 代码不是很多,除了三个必要的构造方法外,在onMeasure方法中,我们给控件设置了一下大小,具体设置可以参考这篇博客

Android之:了解MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST)

博客里对该属性的说明如下:

 MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。

通过Android自带的日历Calendar来实现每月的第一天是星期几,以及每月的总天数,还有当前年月日的获取。

为了方便使用,我们定义了一个DateUtil工具类,里面定义了实现上述几个功能的方法,由于篇幅限制,这里就不贴完代码了,感兴趣的小伙伴可以在文章的底部找到项目的github地址。

核心算法部分,通过查询本地数据库的签到数据,与当前界面的年月日进行匹配,若当前页面含有数据库的签到数据,或者说是与数据库的数据有匹配上的,就将GridView中的Item里面的打勾图片显示出来,表示该日期已签到。

重头戏来了

对于我们自定义的日期控件SignView,既然是继承自GridView的,适配器adapter的定义可以说是最重要,也是最难的。因为大部分的功能都在这里实现了。

话不多说,先来看代码:

public class DateAdapter extends BaseAdapter {

    private static final String TAG = "DateAdapter--->>>";

    private Context context;
    //日历坐标数据 根据布尔值设置是否签到
    private List<Integer> days = new ArrayList<>();
    //签到状态,用来判断坐标中哪个位置是已经签到的
    private List<Boolean> status = new ArrayList<>();

    private List<String> signIns;//数据库查到的签到记录
    private  int maxDay,firstDay,dif; //

    private int current_year, current_mon;

    private SignInHelper helper;

    public interface OnSignListener {
        void OnSignedSucceed();
        void OnSignedFail();
    }

    //签到成功的回调方法,相应的可自行添加签到失败时的回调方法

    public DateAdapter(Context context, int year, int month) {
        this.context = context;

        current_year = year;
        current_mon = month;

         helper = new SignInHelper(context);

        signIns = helper.query(year,month);


        Log.i(TAG, "查到的数据:"+signIns);

        maxDay = DateUtil.getCurrentMonthLastDay(year,month);//获取当月天数

        //firstDay(1-7)  获取当月第一天是星期几,星期日是第一天, 数字为1,代表这个月的第一天是星期天,依次类推
        firstDay = DateUtil.getFirstDayOfMonth(year,month);

        //Log.i(TAG, "year:"+year+" mon:"+month+" firstDay:"+firstDay+" maxDay:"+maxDay);

        //dif,实际定义的status坐标与日期是有差异的,经过实验和对比后,发现在显示已经签到的日期格子时,
        //显示的位置与实际位置总是有差别,即有时候在签到日期是8号,但是打勾的是6号日期的格子。经过查询规律发现,
        //这个偏差是与firstDay变量有关的,而这个偏差的值为firstDay -2, firstDay取值范围为1-7,这样的话
        //dif取值范围即为(-1,5) 当该月的第一天为星期一时,此时firstDay取值为2,dif为0,此时才不存在偏差 否则
        //其他情况都存在偏差(例如Calendar中默认星期天为第一天,此时的Calendar.DAY_OF_WEEK取值为 1 但
        // gridView默认坐标从0开始,此时需要加上偏差 这时的偏差dif= 1-2=-1)
        dif = firstDay -2;

        for (int i = 0; i < firstDay - 1; i++) {
            days.add(0);
            //0代表需要隐藏的item
            status.add(false);
            //false代表为签到状态
        }

        for (int i = 0; i < maxDay; i++) {
            days.add(i+1);
            //初始化日历数据
            status.add(false);
            //初始化日历签到状态
        }
        status = DateUtil.dateConvert(current_year, current_mon,signIns,status,dif);
    }

    @Override
    public int getCount() {
        return days.size();
    }

    @Override
    public Object getItem(int i) {
        return days.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(final int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if(view==null){
            view = LayoutInflater.from(context).inflate(R.layout.item_gv,null);
            viewHolder = new ViewHolder();
            view.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.tv = view.findViewById(R.id.tvWeek);
        viewHolder.rlItem = view.findViewById(R.id.rlItem);
        viewHolder.ivStatus = view.findViewById(R.id.ivStatus);
        viewHolder.tv.setText(days.get(i)+"");
        if(days.get(i)==0){  //接着上个月的残留日期
            viewHolder.rlItem.setVisibility(View.GONE);
        }
        if(status.get(i)){
            viewHolder.tv.setTextColor(Color.parseColor("#FD0000"));
            viewHolder.ivStatus.setVisibility(View.VISIBLE);
        }else{
            viewHolder.tv.setTextColor(Color.parseColor("#666666"));
            viewHolder.ivStatus.setVisibility(View.GONE);
        }
        return view;
    }

    class ViewHolder{
        RelativeLayout rlItem;
        TextView tv;
        ImageView ivStatus;
    }

    public void signIn(OnSignListener listener){
        helper.insert(DateUtil.CURRENT);
        notifyDataSetChanged();
        listener.OnSignedSucceed();
    }
    public boolean isSign(){
        return status.get(DateUtil.DAY+dif);
    }

}
 

代码有点多,逐一解释吧。首先我们将days设置为数据源,以上面第一张图为例,当前月份中,一号为星期六,此时也就意味着,前面的六个位置是没有数据的,对于没有数据的格子,我们将其设置为0。然后在获取视图即getView方法中,通过数据源是否为0控制视图的显示与隐藏。此外还通过一个布尔值类型的List,status辅助记录签到情况,对于已经签到的日期,值设置为true,至于代码中涉及到的偏差dif,逻辑上理解起来可能会有些绕,相关注释在代码中已经写有,这里不再赘述。

关于往月的签到情况查看,由于android6.0之后的DatePickerDialog都是默认会有年月日三项,但是我们只需要年和月这两项,所以我们需要自定义一个只有年和月选项的日期选择器MonPickerDialog,同理也是通过重载DatePickerDialog实现。

将MonPickerDialog定义如下:

public class MonPickerDialog extends DatePickerDialog {

    private OnDateSetListener listener;
    public MonPickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
        super(context, android.R.style.Theme_Holo_Light_Panel, callBack,year, monthOfYear, dayOfMonth);
        this.setTitle(year + "年" + (monthOfYear + 1) + "月");
        listener = callBack;
        ((ViewGroup) ((ViewGroup) this.getDatePicker().getChildAt(0)).getChildAt(0)).getChildAt(2).
                setVisibility(View.GONE);
    }

    @Override
    public void onDateChanged(DatePicker view, int year, int month, int day) {
        super.onDateChanged(view, year, month, day);
        this.setTitle(year + "年" + (month + 1) + "月");
        listener.onDateSet(view,year,month,day);
    }
}

 效果如下图:

项目已经发布到github:https://github.com/Domlaa/SignView

如果喜欢的话别忘了点个star,谢谢^_^

发布了7 篇原创文章 · 获赞 7 · 访问量 2746

猜你喜欢

转载自blog.csdn.net/qq_38527695/article/details/91355278