My goal is to find an Android library that will allow me to mark various dates on a calendar view based on an array. The dates may or may not be contiguous. My ideal scenario is to change the background color of each date. The significant complication is that I don't know this color until runtime, since it will come from a server query.
I've been researching this all day, and my best hope seems to be material-calendarview (github). However, I am finding their code to be somewhat impenetrable, which is on me, but I am completely stuck.
I've added a calendar like this in my XML layout:
<com.prolificinteractive.materialcalendarview.MaterialCalendarView
android:id="#+id/calendar_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:mcv_showOtherDates="all"
app:mcv_selectionColor="#00F"/>
And then in my activity, I have these instance variables:
private MaterialCalendarView calendarView;
private ArrayList<Date> markedDates;
and this code in my onCreateView()
calendarView = (MaterialCalendarView) view.findViewById(R.id.calendar_view);
Ok, easy enough. But I cannot figure out how to mark the calendar from my array of dates. I am working on this method, but I just don't know how to proceed beyond what I have here:
private void initializeCalendar() {
calendarView.setOnDateChangedListener(context);
calendarView.setShowOtherDates(MaterialCalendarView.SHOW_ALL);
Calendar calendar = Calendar.getInstance();
calendarView.setSelectedDate(calendar.getTime());
calendar.set(calendar.get(Calendar.YEAR), Calendar.JANUARY, 1);
calendarView.setMinimumDate(calendar.getTime());
calendar.set(calendar.get(Calendar.YEAR), Calendar.DECEMBER, 31);
calendarView.setMaximumDate(calendar.getTime());
int bgColor = sharedVisualElements.getPrimaryColor();
calendarView.addDecorators(new EventDecorator(bgColor, ????));
}
That last line refers to this inner class:
private class EventDecorator implements DayViewDecorator {
private final int color;
private final HashSet<CalendarDay> dates;
public EventDecorator(int color, Collection<CalendarDay> dates) {
this.color = color;
this.dates = new HashSet<>(dates);
}
#Override
public boolean shouldDecorate(CalendarDay day) {
return dates.contains(day);
}
#Override
public void decorate(DayViewFacade view) {
view.addSpan(new DotSpan(5, color));
}
}
I think that my challenge to convert my ArrayList<Date> markedDates to what they call Collection<CalendarDay> dates. Agree? But this is where I really bog down. This data structure is bizarre to me. When I try to instantiate it by calling new CalendarDay() my class immediately expands with about 10 new methods that I don't understand their role or what to do with them. Clearly, I am going off the rails here. It just can't be this tricky.
Has anyone used this library for this purpose and know how to accomplish this task? I'm at a grinding halt. Also, if there is a simpler library to allow me to set background colors using a color only known at run-time, I'm all ears.
Thanks for any help. I fear that I have written this in a confusing manner, which is a result of the fact that I am completely confused.
I solved this, so I'll post that solution in case anyone else has the same question. If there is a more efficient way, please post as a solution.
I mentioned that I have an array with a list of dates. What I need to do is to iterate over that array, converting each Date into a Calendar object set to the appropriate year, month, and day, and then adding that object to a different ArrayList, this time an ArrayList<CalendarDay>. For example:
List<CalendarDay> list = new ArrayList<CalendarDay>();
Calendar calendar = Calendar.getInstance();
for (Date date : markedDates) {
// might be a more elegant way to do this part, but this is very explicit
int year = date.getYear();
int month = date.getMonthOfYear() - 1; // months are 0-based in Calendar
int day = date.getDayOfMonth();
calendar.set(year, month, day);
CalendarDay calendarDay = CalendarDay.from(calendar);
list.add(calendarDay);
}
So now we've got this list of CalendarDay objects, but we're not quite there. The final step in creating the data structure is to 'convert' this over to what I mentioned I was struggling with in the OP - a Collection<CalendarDay> structure. Turns out this couldn't be any simpler once we get here. Simply assign it like this:
calendarDays = list;
And then when you want to add the decorator, you're all set up. Just do this:
calendarView.addDecorators(new EventDecorator(myColor, calendarDays));
One other thing bears mentioning, and this was a major source of my confusion. I didn't understand how to instantiate this Collection<CalendarDay> object. Way up in the instance variable section (before the constructor), I added this code, almost all of which Android Studio filled in for me:
private Collection<CalendarDay> calendarDays = new Collection<CalendarDay>() {
#Override
public boolean add(CalendarDay object) {
return false;
}
#Override
public boolean addAll(Collection<? extends CalendarDay> collection) {
return false;
}
#Override
public void clear() {
}
#Override
public boolean contains(Object object) {
return false;
}
#Override
public boolean containsAll(Collection<?> collection) {
return false;
}
#Override
public boolean isEmpty() {
return false;
}
#NonNull
#Override
public Iterator<CalendarDay> iterator() {
return null;
}
#Override
public boolean remove(Object object) {
return false;
}
#Override
public boolean removeAll(Collection<?> collection) {
return false;
}
#Override
public boolean retainAll(Collection<?> collection) {
return false;
}
#Override
public int size() {
return 0;
}
#NonNull
#Override
public Object[] toArray() {
return new Object[0];
}
#NonNull
#Override
public <T> T[] toArray(T[] array) {
return null;
}
};
I hope that this helps someone. Again, if there is a better solution, please post and I'll delete mine.
If you want to change the background color of the selected programatically, use this method:-
MaterialCalendarView materialCalendar = (MaterialCalendarView)findViewById(R.id.materialCalenderView);
materialCalendar.setSelectionColor(Color.parseColor("#00BCD4"));
Using this code makes all selectors of same color, so if you want to have different color selectors depending on your condition, use decorator()
Why did you use the ArrayList instead of the HashSet ?
The reason you cannot instantiate Collection is because it is an interface, hence you had to create anonymous class and override the methods.
Here is how I did something similar:
This method takes in two Calendar objects and adds all the days in between the two Calendar dates into a HashSet of CalendarDays.
private HashSet<CalendarDay> getCalendarDaysSet(Calendar cal1, Calendar cal2) {
HashSet<CalendarDay> setDays = new HashSet<>();
while (cal1.getTime().before(cal2.getTime())) {
CalendarDay calDay = CalendarDay.from(cal1);
setDays.add(calDay);
cal1.add(Calendar.DATE, 1);
}
return setDays;
}
on the onCreateView(...) method,I have dynamically set the two calendar dates, the difference between which will be stored in the HashSet. However, you can pass your own random set of dates in the HashSet.
Calendar cal1 = Calendar.getInstance();
cal1.set(2016, 8, 1);
Calendar cal2 = Calendar.getInstance();
cal2.set(2016, 9, 1);
HashSet<CalendarDay> setDays = getCalendarDaysSet(cal1, cal2);
int myColor = R.color.red;
mCalendarView.addDecorator(new BookingDecorator(myColor, setDays));
In my case BookingDecorator is the class that implements the DayViewDecorator interface.
private class BookingDecorator implements DayViewDecorator {
private int mColor;
private HashSet<CalendarDay> mCalendarDayCollection;
public BookingDecorator(int color, HashSet<CalendarDay> calendarDayCollection) {
mColor = color;
mCalendarDayCollection = calendarDayCollection;
}
#Override
public boolean shouldDecorate(CalendarDay day) {
return mCalendarDayCollection.contains(day);
}
#Override
public void decorate(DayViewFacade view) {
view.addSpan(new ForegroundColorSpan(mColor));
//view.addSpan(new BackgroundColorSpan(Color.BLUE));
view.setBackgroundDrawable(ContextCompat.getDrawable(getContext(),R.drawable.greenbox));
}
}
Your post was very helpful. Hope mine helps somebody as well.
Related
I am now making a DiffUtil class to update only changed items in the RecyclerView.
I have seen several other sample code.
When comparing two objects, they compared unique values such as id defined in the Model(Data) class in areItemsTheSame().
However, I think it is difficult to assign an id or unique value to the List, or the code is messy.
Do I have to define and compare id like this?
Do I really need to define a unique Id variable in the Model class that separates each object?
Or shouldn't I use simply the equals()?
Using this Is it not just comparing the address of the object, but also the contents of the object?
As an additional question
What is the difference between DiffUtil.CallBack and DiffUtil.ItemCallBack?
This is my code.
RoutineModel.java
public class RoutineModel {
private ArrayList<RoutineDetailModel> routineDetailModels;
private String routine;
public RoutineModel(ArrayList<RoutineDetailModel> items, String routine) {
this.routine = routine;
this.routineDetailModels = items;
}
public ArrayList<RoutineDetailModel> getDetailItemList() {
return routineDetailModels;
}
public int getDetailItemSize() {
return routineDetailModels.size();
}
public String getRoutine() {
return routine;
}
public void setRoutine(String routine) {
this.routine = routine;
}
}
RoutineDiffUtil.java
public class RoutineDiffUtil extends DiffUtil.Callback {
private final List<RoutineModel> oldRoutineList;
private final List<RoutineModel> newRoutineList;
public RoutineDiffUtil(ArrayList<RoutineModel> oldRoutineList, ArrayList<RoutineModel> newRoutineList) {
this.oldRoutineList = oldRoutineList;
this.newRoutineList = newRoutineList;
}
#Override
public int getOldListSize() {
return oldRoutineList.size();
}
#Override
public int getNewListSize() {
return newRoutineList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldRoutineList.equals(newRoutineList);
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldRoutineList.equals(newRoutineList);
}
}
You got wrong the meaning of areItemsTheSame() and areContentsTheSame() callbacks. As you see, there are oldItemPosition and newItemPosition arguments in them. You should use them to compare specific items – not lists themselves.
In areItemsTheSame() you have to check whether model at "old" position in the old list equals a model at "new" position in the new list. This is how DiffUtil knows if it has to make reordering animations.
areContentsTheSame() will be called for two items if and only if you return true for them in the previous callback. Here you have to check whether visual representation of "old" and "new" models is the same. This is how DiffUtil knows if it has to make "item changing" animations.
To compare two models you have to override equals() and hashCode(). There you specify conditions under which you consider two models the same. For example, if they have same routine. I don't the know context of your task so I can't tell you exactly how to implement them, but usually you just compare all fields. Probably adding an id field is a good idea too. Then you can consider models "equal" if they have same id. And in hashCode() you can just return Objects.hash(id).
Now, speaking about your question about ItemCallback. Formally, here is the explanation from docs:
DiffUtil.Callback serves two roles - list indexing, and item diffing. ItemCallback handles just the second of these, which allows separation of code that indexes into an array or List from the presentation-layer and content specific diffing code.
Practically, ItemCallback just has less methods to implement and is used together with AsyncListDiffer. It's just because missing methods are already implemented under the hood in AsyncListDiffer.
You have to override the equals and hashcodes of your model classes.
RoutineModel:
class RoutineModel {
private ArrayList<RoutineDetailModel> routineDetailModels;
private String routine;
public RoutineModel(ArrayList<RoutineDetailModel> items, String routine) {
this.routine = routine;
this.routineDetailModels = items;
}
public ArrayList<RoutineDetailModel> getDetailItemList() {
return routineDetailModels;
}
public int getDetailItemSize() {
return routineDetailModels.size();
}
public String getRoutine() {
return routine;
}
public void setRoutine(String routine) {
this.routine = routine;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RoutineModel that = (RoutineModel) o;
return Objects.equals(routineDetailModels, that.routineDetailModels) &&
Objects.equals(routine, that.routine);
}
#Override
public int hashCode() {
return Objects.hash(routineDetailModels, routine);
}
}
RoutineDiffUtil:
public class RoutineDiffUtil extends DiffUtil.Callback {
private final List<RoutineModel> oldRoutineList;
private final List<RoutineModel> newRoutineList;
public RoutineDiffUtil(ArrayList<RoutineModel> oldRoutineList, ArrayList<RoutineModel> newRoutineList) {
this.oldRoutineList = oldRoutineList;
this.newRoutineList = newRoutineList;
}
#Override
public int getOldListSize() {
return oldRoutineList.size();
}
#Override
public int getNewListSize() {
return newRoutineList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldRoutineList.get(oldItemPosition).getRoutine().equals(newRoutineList.get(newItemPosition).getRoutine());
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldRoutineList.get(oldItemPosition).equals(newRoutineList.get(newItemPosition));
}
}
And don't forget to override the equals and hashcode of your RoutineDetailModel.
There is a vey nice libray(https://github.com/Applandeo/Material-Calendar-View) for customizing the android calenderview . Like for example, for adding events to the calenderview there is class named Eventday.java, which takes calender object and a drawable object as parameters to initialize. I wish it could have take a string value too so that i can also store a descrption of the event, because there is no point in adding events to the calender if one cannot add details about the event(string datatype). Can some one provide a workaround for my problem?
List<EventDay> events = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
//we cannot details about the event(string data) while initializing the EventDay class
events.add(new EventDay(calendar, R.drawable.absentic));
First Make a Custom Class
import com.applandeo.materialcalendarview.EventDay;
import java.util.Calendar;
public class MyEventDay extends EventDay implements Parcelable {
private String mNote;
public MyEventDay(Calendar day, int imageResource, String note) {
super(day, imageResource);
mNote = note;
}
public String getNote() {
return mNote;
}
private MyEventDay(Parcel in) {
super((Calendar) in.readSerializable(), in.readInt());
mNote = in.readString();
}
public static final Creator<MyEventDay> CREATOR = new Creator<MyEventDay>() {
#Override
public MyEventDay createFromParcel(Parcel in) {
return new MyEventDay(in);
}
#Override
public MyEventDay[] newArray(int size) {
return new MyEventDay[size];
}
};
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeSerializable(getCalendar());
parcel.writeInt(getImageResource());
parcel.writeString(mNote);
}
#Override
public int describeContents() {
return 0;
}
}
Adding Event In Calender
mEventDays.add(new MyEventDay(calender, R.drawable.ic_note_sticky, "I am Event");
For Retriving String
calendarView.setOnDayClickListener(new OnDayClickListener() {
#Override
public void onDayClick(EventDay eventDay) {
Log.e("Event",((MyEventDay) eventDay).getNote()+" <--");
}
});
Its Work For me , I Hope its Helpfull to you also
I used this method to display small popup note when user clicks on any date.
Declared: Map<Date,String> eventNotes = new HashMap<>()
While updating calendar events for every date, populated the eventNotes with required notes and date as key
In Calendar setOnDayClickListener retrieved eventNote using the key eventDay.getCalendar().getTime()
Then used a dialog popup to show that note
suppose I have defined a List as
private BlockingQueue<MyDelayed> DelayedIds = new DelayQueue<>();
class MyDelayed is like:
private class MyDelayed implements Delayed {
private String myId;
private Long creationTime;
MyDelayed (String myId) {
this.myId= myId;
this.creationTime = System.currentTimeMillis();
}
String getMyId() {
return this.myId;
}
#Override
public long getDelay(TimeUnit unit) {
//TODO
}
#Override
public int compareTo(Delayed o) {
//TODO
}
}
Now suppose that I want to add an Object of class MyDelayed in DelayedIds list.
I can do it by using add function.
But If I want to add obbject in list only if list does not contain an object of class MyDelayed which has the same myId attribute which I am trying to insert.
Obviously DelayedIds .contains(new MyDelayed(myId)) will not work.
Is there any easy way to check this thing ?
Am I missing something ?
You could write something like this and compare every element in the list to see if it contains your id. If at any point you find a matching one you return true, if the loop finished having found none it returns false.
public boolean contains(String id){
for (MyDelayed md : DelayedIds){
if(md.getMyId().equals(id)){
return true;
}
}
return false;
}
Now to check before adding you would do something like:
if(!contains(myNewObject.getMyId())){
DelayedIds.add(myNewObject)
}
Also, I'd suggest that you rename DelayedIds to delayedIds in order to follow coding standards (see Variables).
I want to make a simple note reminder app that uses hashMap when the date is the key and the value is the text.
I have a Panel class(GUI), the hash-table class (Reminder.java), and a "MyDateClass.java" that represent a date for my purposes.
My gui is made of 3 JComboBox (day,month,year), One text area and 2 buttons - "Save", "Load".
The 2 buttons in the GUI Panel:
butSave.addActionListener(new ActionListener () {
public void actionPerformed(ActionEvent e) {
MyDateClass chosenDate = new MyDateClass(cbYear.getSelectedIndex()+2013,cbMonth.getSelectedIndex()+1, cb.getSelectedIndex()+1);
if(!remind.isReminderExists(chosenDate)){
remind.save(chosenDate, tfReminder.getText());
System.out.println("reminder doesnt exists");
}}
});
butLoad.addActionListener(new ActionListener () {
public void actionPerformed(ActionEvent e) {
System.out.println("tryin to load");
MyDateClass chosenDate = new MyDateClass(cbYear.getSelectedIndex()+2013,cbMonth.getSelectedIndex()+1, cb.getSelectedIndex()+1);
if(remind.isReminderExists(chosenDate)){
remind.Load(chosenDate);
System.out.println("reminder exists");
}}
});
Reminder class:
public class Reminder {
Map<MyDateClass,String> reminderMap;
public Reminder(){
reminderMap = new HashMap<MyDateClass,String>();
}
public boolean isReminderExists(MyDateClass date){
return reminderMap.containsKey(date);
}
public void save(MyDateClass date, String Input){
System.out.println("Trying to save");
reminderMap.put(date, Input);
}
public void Load(MyDateClass date){
System.out.println("Trying to load");
String output;
output = reminderMap.get(date);
System.out.println(output);
}
So after i push the save button i get from the console:
Trying to save
reminder doesnt exists
But then i push the Load button for the same date and
if(remind.isReminderExists(chosenDate))
Isnt triggerd.
What might be the problem?
Do i need to override hashCode() and equals() ? I genereted them but i dont if and how to change the equals() (do i need to manipulate it to compare both dates? How do i do that if "this" refers to the reminder Object)
Try something like this.
#Override public boolean equals(Object o) {
if(o == this) return true;
if(!(o instanceof MyDateClass)) return false;
MyDateClass that = (MyDateClass) o;
// use == for primitives
// use .compare for primitive wrappers where available
// use .equals for objects
return this.ivar1 == that.ivar2 &&
this.ivar2 == that.ivar2; //etc...
}
// equal objects must have equal hash codes
#Override public int hashCode() {
int result = 17;
result = 31 * result + ivar1;
result = 31 * result + ivar2;
return result;
}
Write JUnits to test for reflexive, symmetric, transitive, and consistent results.
As I know your problem clearly, I think that this function always return false. Because date(parameter) and the object in reminderMap have difference reference. So they can not be equal.
public boolean isReminderExists(MyDateClass date){
return reminderMap.containsKey(date);
}
If you want to use containsKey function of HashMap, maybe you should use String or Number instead of MyDateClass. I mean you should convert object of MyDateClass to String value before inserting it into reminderMap.
public boolean isReminderExists(String date){
return reminderMap.containsKey(date);
}
Otherwise, you can implement your code as Hot Licks mention.
I am relatively inexperienced with java & generics, so please excuse me if this is a stupid question.
I have 3 very similar helper methods called verifyTextualSort, verifyNumericSort and verifyDateSort.
The 3 methods follow the same pattern with only a slight difference in them:
private boolean verifyTextualSort(...) {
ArrayList<String> list = new ArrayList<String>();
// Do common stuff with the list
// Do textual-specific stuff
// Do common stuff with the list
}
private boolean verifyNumericSort(...) {
ArrayList<Integer> list = new ArrayList<Integer>();
// Do common stuff with the list
// Do Numeric-specific stuff
// Do common stuff with the list
}
Is there some way I can combine them into one method, passing somehow the type (Integer, String, Date) as a parameter? I have to be able to know which is the type from inside the method so that I can do the correct specific stuff.
You need three method for the specific stuff. However for the common stuff you can create a common method they both call.
private boolean verifyNumericSort(...) {
List<Integer> list = new ArrayList<Integer>();
commonStuff1(list);
// Do Numeric-specific stuff
commonStuff2(list);
}
You could pass a Class as a parameter, if that is what you want (as you said, passing the type as a parameter):
public <T> void test(List<T> l, T t, Class<T> c) {
System.out.println(c.getName());
System.out.println(l.get(0).getClass().getName());
System.out.println(t.getClass().getName());
}
All the sysouts above will print out the name of the class, so you'll be able to choose which one suits you the best.
You can't do that by introspection using the Generics because of type erasure. But if the list is not empty, you can check the type of the first element and then invoke appropriate method.
since you have 3 fields you can do this..
class A
{
private Date date = null;
private Integer int = null;
private String text = null;
//add getters and setters for these fields
}
and now pass this class Object as an arguement to that method
public boolean verify(A a){
a.getDate();
a.getInt()
//etc and do your stuff
}
You need generics and refactoring:
private boolean verifyTextualSort(List<String> strings) {
commonStuffA(strings);
// Do textual-specific stuff
commonStuffB(strings);
return true; // ?
}
private boolean verifyNumericSort(List<Integer> ints) {
commonStuffB(ints);
// Do Numeric-specific stuff
commonStuffB(ints);
return true; // ?
}
private void commonStuffA(List<?> things) { // This method accept a list of anything
// Do common stuff A with the list
}
private void commonStuffB(List<?> things) { // This method accept a list of anything
// Do common stuff B with the list
}
private void someCallingMethod() {
List<String> strings = new ArrayList<String>();
verifyTextualSort(strings);
List<Integer> ints = new ArrayList<Integer>();
verifyTextualSort(ints);
}
I think you could possibly do something similar to this:
public <T extends Object> boolean verify(T t)
{
if(!(t==null))
{
if(t instanceof Date)
{
//Do date verify routine
return true;
}
else if(t instanceof String)
{
//Do String verify routine
return true;
}
else
{
//Do default verify routine which could be Integer
return true;
}
}
return false;
}
NOTE:
This is not tested.
As others have mentioned, you can't do that with generics because of type erasure (see the other answers for a link to type erasure). I believe you can get a reasonable solution (without instanceof) with polymorphism. Here is an example:
public class VerifySort
{
public static void main(String[] args)
{
VerifySort verifySort = new VerifySort();
Date testDate = new Date();
Integer testInteger = 17;
String testString = "Blammy";
verifySort.verify(testString);
verifySort.verify(testInteger);
verifySort.verify(testDate);
}
private boolean verify(Date parameter)
{
SimpleDateFormat dateFormat = new SimpleDateFormat();
System.out.print("Date parameter: ");
System.out.println(dateFormat.format(parameter));
return true;
}
private boolean verify(Integer parameter)
{
System.out.print("Integer parameter: ");
System.out.println(parameter);
return true;
}
private boolean verify(String parameter)
{
System.out.print("String parameter: ");
System.out.println(parameter);
return true;
}