Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 518ff0de authored by Alan Viverette's avatar Alan Viverette
Browse files

Fix date and time picker styling

Removes done buttons from widgets, fixes date picker day selection, fixes
dialog layouts in landscape, updates colors. Adds API on AlertDialog for
setting the view by resource ID, which is necessary to correctly inflate
the view against the dialog's parent view.

BUG: 16941550
BUG: 16852521
BUG: 16878697
BUG: 16838659
BUG: 17047435
Change-Id: I138858ce06cd4abf68a2c3361ec170370236b33b
parent 4473ec5a
Loading
Loading
Loading
Loading
+34 −50
Original line number Diff line number Diff line
@@ -24,8 +24,10 @@ import android.text.format.DateUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.DatePicker.OnDateChangedListener;
import android.widget.DatePicker.ValidationCallback;

import com.android.internal.R;

@@ -49,7 +51,6 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
    private final Calendar mCalendar;

    private boolean mTitleNeedsUpdate = true;
    private boolean mIsCanceled = false;

    /**
     * The callback used to indicate the user is done filling in the date.
@@ -83,7 +84,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,

    static int resolveDialogTheme(Context context, int resid) {
        if (resid == 0) {
            TypedValue outValue = new TypedValue();
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
            return outValue.resourceId;
        } else {
@@ -99,69 +100,42 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
     * @param monthOfYear The initial month of the dialog.
     * @param dayOfMonth The initial day of the dialog.
     */
    public DatePickerDialog(Context context,
            int theme,
            OnDateSetListener listener,
            int year,
            int monthOfYear,
            int dayOfMonth) {
    public DatePickerDialog(Context context, int theme, OnDateSetListener listener, int year,
            int monthOfYear, int dayOfMonth) {
        super(context, resolveDialogTheme(context, theme));

        mDateSetListener = listener;
        mCalendar = Calendar.getInstance();

        Context themeContext = getContext();

        final LayoutInflater inflater = (LayoutInflater) themeContext.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        final Context themeContext = getContext();
        final LayoutInflater inflater = LayoutInflater.from(themeContext);
        final View view = inflater.inflate(R.layout.date_picker_dialog, null);
        setView(view);
        setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
        setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
        setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);

        // Initialize state
        mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
        mDatePicker.setShowDoneButton(true);
        mDatePicker.setDismissCallback(new DatePicker.DatePickerDismissCallback() {
            @Override
            public void dismiss(DatePicker view, boolean isCancel, int year, int month, int dayOfMonth) {
                mIsCanceled = isCancel;
                if (!isCancel) {
                    mDateSetListener.onDateSet(view, year, month, dayOfMonth);
                }
                DatePickerDialog.this.dismiss();
            }
        });
        mDatePicker.init(year, monthOfYear, dayOfMonth, this);
    }

    public void onClick(DialogInterface dialog, int which) {
        tryNotifyDateSet();
        mDatePicker.setValidationCallback(mValidationCallback);
    }

    @Override
    public void cancel() {
        mIsCanceled = true;
        super.cancel();
    }

    @Override
    protected void onStop() {
        tryNotifyDateSet();
        super.onStop();
    }

    public void onDateChanged(DatePicker view, int year,
            int month, int day) {
    public void onDateChanged(DatePicker view, int year, int month, int day) {
        mDatePicker.init(year, month, day, this);
        updateTitle(year, month, day);
    }

    private void tryNotifyDateSet() {
        if (mDateSetListener != null && !mIsCanceled) {
            mDatePicker.clearFocus();
    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
            case BUTTON_POSITIVE:
                if (mDateSetListener != null) {
                    mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
                            mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
                }
                break;
        }
    }

    /**
@@ -208,7 +182,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,

    @Override
    public Bundle onSaveInstanceState() {
        Bundle state = super.onSaveInstanceState();
        final Bundle state = super.onSaveInstanceState();
        state.putInt(YEAR, mDatePicker.getYear());
        state.putInt(MONTH, mDatePicker.getMonth());
        state.putInt(DAY, mDatePicker.getDayOfMonth());
@@ -218,9 +192,19 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        int year = savedInstanceState.getInt(YEAR);
        int month = savedInstanceState.getInt(MONTH);
        int day = savedInstanceState.getInt(DAY);
        final int year = savedInstanceState.getInt(YEAR);
        final int month = savedInstanceState.getInt(MONTH);
        final int day = savedInstanceState.getInt(DAY);
        mDatePicker.init(year, month, day, this);
    }

    private final ValidationCallback mValidationCallback = new ValidationCallback() {
        @Override
        public void onValidationChanged(boolean valid) {
            final Button positive = getButton(BUTTON_POSITIVE);
            if (positive != null) {
                positive.setEnabled(valid);
            }
        }
    };
}
+53 −67
Original line number Diff line number Diff line
@@ -19,13 +19,14 @@ package android.app;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Build;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
import android.widget.TimePicker.ValidationCallback;

import com.android.internal.R;

@@ -35,8 +36,19 @@ import com.android.internal.R;
 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
 * guide.</p>
 */
public class TimePickerDialog extends AlertDialog
        implements OnClickListener, OnTimeChangedListener {
public class TimePickerDialog extends AlertDialog implements OnClickListener,
        OnTimeChangedListener {

    private static final String HOUR = "hour";
    private static final String MINUTE = "minute";
    private static final String IS_24_HOUR = "is24hour";

    private final TimePicker mTimePicker;
    private final OnTimeSetListener mTimeSetCallback;

    private final int mInitialHourOfDay;
    private final int mInitialMinute;
    private final boolean mIs24HourView;

    /**
     * The callback interface used to indicate the user is done filling in
@@ -52,19 +64,6 @@ public class TimePickerDialog extends AlertDialog
        void onTimeSet(TimePicker view, int hourOfDay, int minute);
    }

    private static final String HOUR = "hour";
    private static final String MINUTE = "minute";
    private static final String IS_24_HOUR = "is24hour";

    private final TimePicker mTimePicker;
    private final OnTimeSetListener mTimeSetCallback;

    int mInitialHourOfDay;
    int mInitialMinute;
    boolean mIs24HourView;

    private boolean mIsCanceled;

    /**
     * @param context Parent.
     * @param callBack How parent is notified.
@@ -80,7 +79,7 @@ public class TimePickerDialog extends AlertDialog

    static int resolveDialogTheme(Context context, int resid) {
        if (resid == 0) {
            TypedValue outValue = new TypedValue();
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
            return outValue.resourceId;
        } else {
@@ -96,10 +95,8 @@ public class TimePickerDialog extends AlertDialog
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(Context context,
            int theme,
            OnTimeSetListener callBack,
            int hourOfDay, int minute, boolean is24HourView) {
    public TimePickerDialog(Context context, int theme, OnTimeSetListener callBack, int hourOfDay,
            int minute, boolean is24HourView) {
        super(context, resolveDialogTheme(context, theme));

        mTimeSetCallback = callBack;
@@ -108,72 +105,51 @@ public class TimePickerDialog extends AlertDialog
        mIs24HourView = is24HourView;

        final Context themeContext = getContext();

        final int targetSdkVersion = themeContext.getApplicationInfo().targetSdkVersion;
        if (targetSdkVersion < Build.VERSION_CODES.L) {
            setIcon(0);
            setTitle(R.string.time_picker_dialog_title);
        }

        final LayoutInflater inflater = LayoutInflater.from(themeContext);
        final View view = inflater.inflate(R.layout.time_picker_dialog, null);
        setView(view);
        setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
        setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);

        mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
        mTimePicker.setShowDoneButton(true);
        // If time picker layout has no done button, add a dialog button.
        if (!mTimePicker.isShowDoneButton()) {
            setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
        }

        mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
            @Override
            public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) {
                mIsCanceled = isCancel;
                if (!isCancel) {
                    mTimeSetCallback.onTimeSet(view, hourOfDay, minute);
                    TimePickerDialog.this.dismiss();
                } else {
                    TimePickerDialog.this.cancel();
                }
            }
        });
        mTimePicker.setIs24HourView(mIs24HourView);
        mTimePicker.setCurrentHour(mInitialHourOfDay);
        mTimePicker.setCurrentMinute(mInitialMinute);
        mTimePicker.setOnTimeChangedListener(this);
        mTimePicker.setValidationCallback(mValidationCallback);
    }

    public void onClick(DialogInterface dialog, int which) {
        tryNotifyTimeSet();
    }

    public void updateTime(int hourOfDay, int minutOfHour) {
        mTimePicker.setCurrentHour(hourOfDay);
        mTimePicker.setCurrentMinute(minutOfHour);
    }

    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
        /* do nothing */
    }

    private void tryNotifyTimeSet() {
        if (mTimeSetCallback != null && !mIsCanceled) {
            mTimePicker.clearFocus();
    @Override
    public void onClick(DialogInterface dialog, int which) {
        switch (which) {
            case BUTTON_POSITIVE:
                if (mTimeSetCallback != null) {
                    mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
                            mTimePicker.getCurrentMinute());
                }
                break;
        }
    }

    @Override
    protected void onStop() {
        tryNotifyTimeSet();
        super.onStop();
    /**
     * Sets the current time.
     *
     * @param hourOfDay The current hour within the day.
     * @param minuteOfHour The current minute within the hour.
     */
    public void updateTime(int hourOfDay, int minuteOfHour) {
        mTimePicker.setCurrentHour(hourOfDay);
        mTimePicker.setCurrentMinute(minuteOfHour);
    }

    @Override
    public Bundle onSaveInstanceState() {
        Bundle state = super.onSaveInstanceState();
        final Bundle state = super.onSaveInstanceState();
        state.putInt(HOUR, mTimePicker.getCurrentHour());
        state.putInt(MINUTE, mTimePicker.getCurrentMinute());
        state.putBoolean(IS_24_HOUR, mTimePicker.is24HourView());
@@ -183,10 +159,20 @@ public class TimePickerDialog extends AlertDialog
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        int hour = savedInstanceState.getInt(HOUR);
        int minute = savedInstanceState.getInt(MINUTE);
        final int hour = savedInstanceState.getInt(HOUR);
        final int minute = savedInstanceState.getInt(MINUTE);
        mTimePicker.setIs24HourView(savedInstanceState.getBoolean(IS_24_HOUR));
        mTimePicker.setCurrentHour(hour);
        mTimePicker.setCurrentMinute(minute);
    }

    private final ValidationCallback mValidationCallback = new ValidationCallback() {
        @Override
        public void onValidationChanged(boolean valid) {
            final Button positive = getButton(BUTTON_POSITIVE);
            if (positive != null) {
                positive.setEnabled(valid);
            }
        }
    };
}
+30 −31
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.widget;

import android.annotation.Nullable;
import android.annotation.Widget;
import android.content.Context;
import android.content.res.Configuration;
@@ -123,7 +124,7 @@ public class DatePicker extends FrameLayout {

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
                defStyleAttr, defStyleRes);
        int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
        final int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
        a.recycle();

        switch (mode) {
@@ -148,20 +149,6 @@ public class DatePicker extends FrameLayout {
                defStyleRes);
    }

    /**
     * @hide
     */
    public void setShowDoneButton(boolean showDoneButton) {
        mDelegate.setShowDoneButton(showDoneButton);
    }

    /**
     * @hide
     */
    public void setDismissCallback(DatePickerDismissCallback callback) {
        mDelegate.setDismissCallback(callback);
    }

    /**
     * Initialize the state. If the provided values designate an inconsistent
     * date the values are normalized before updating the spinners.
@@ -259,6 +246,16 @@ public class DatePicker extends FrameLayout {
        mDelegate.setMaxDate(maxDate);
    }

    /**
     * Sets the callback that indicates the current date is valid.
     *
     * @param callback the callback, may be null
     * @hide
     */
    public void setValidationCallback(@Nullable ValidationCallback callback) {
        mDelegate.setValidationCallback(callback);
    }

    @Override
    public void setEnabled(boolean enabled) {
        if (mDelegate.isEnabled() == enabled) {
@@ -402,8 +399,7 @@ public class DatePicker extends FrameLayout {
        void setSpinnersShown(boolean shown);
        boolean getSpinnersShown();

        void setShowDoneButton(boolean showDoneButton);
        void setDismissCallback(DatePickerDismissCallback callback);
        void setValidationCallback(ValidationCallback callback);

        void onConfigurationChanged(Configuration newConfig);

@@ -432,6 +428,7 @@ public class DatePicker extends FrameLayout {

        // Callbacks
        protected OnDateChangedListener mOnDateChangedListener;
        protected ValidationCallback mValidationCallback;

        public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
            mDelegator = delegator;
@@ -447,15 +444,27 @@ public class DatePicker extends FrameLayout {
            }
            mCurrentLocale = locale;
        }

        @Override
        public void setValidationCallback(ValidationCallback callback) {
            mValidationCallback = callback;
        }

        protected void onValidationChanged(boolean valid) {
            if (mValidationCallback != null) {
                mValidationCallback.onValidationChanged(valid);
            }
        }
    }

    /**
     * A callback interface for dismissing the DatePicker when included into a Dialog
     * A callback interface for updating input validity when the date picker
     * when included into a dialog.
     *
     * @hide
     */
    public static interface DatePickerDismissCallback {
        void dismiss(DatePicker view, boolean isCancel, int year, int month, int dayOfMonth);
    public static interface ValidationCallback {
        void onValidationChanged(boolean valid);
    }

    /**
@@ -774,16 +783,6 @@ public class DatePicker extends FrameLayout {
            return mSpinners.isShown();
        }

        @Override
        public void setShowDoneButton(boolean showDoneButton) {
            // Nothing to do
        }

        @Override
        public void setDismissCallback(DatePickerDismissCallback callback) {
            // Nothing to do
        }

        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            setCurrentLocale(newConfig.locale);
+3 −52
Original line number Diff line number Diff line
@@ -31,11 +31,9 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.StateSet;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
@@ -83,8 +81,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
    private DayPickerView mDayPickerView;
    private YearPickerView mYearPickerView;

    private ViewGroup mLayoutButtons;

    private boolean mIsEnabled = true;

    // Accessibility strings.
@@ -106,11 +102,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
    private Calendar mMinDate;
    private Calendar mMaxDate;

    // For showing the done button when in a Dialog
    private Button mDoneButton;
    private boolean mShowDoneButton;
    private DatePicker.DatePickerDismissCallback mDismissCallback;

    private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();

    public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
@@ -165,7 +156,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
                R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor);
        final int headerBackgroundColor = a.getColor(R.styleable.DatePicker_headerBackgroundColor,
                Color.TRANSPARENT);
        mMonthAndDayLayout.setBackgroundColor(headerBackgroundColor);
        mDateLayout.setBackgroundColor(headerBackgroundColor);

        final int monthTextAppearanceResId = a.getResourceId(
                R.styleable.DatePicker_headerMonthTextAppearance, -1);
@@ -221,20 +212,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
        animation2.setDuration(ANIMATION_DURATION);
        mAnimator.setOutAnimation(animation2);

        mLayoutButtons = (ViewGroup) mainView.findViewById(R.id.layout_buttons);
        mDoneButton = (Button) mainView.findViewById(R.id.done);
        mDoneButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tryVibrate();
                if (mDismissCallback != null) {
                    mDismissCallback.dismiss(mDelegator, false, mCurrentDate.get(Calendar.YEAR),
                            mCurrentDate.get(Calendar.MONTH),
                            mCurrentDate.get(Calendar.DAY_OF_MONTH));
                }
            }
        });

        updateDisplay(false);
        setCurrentView(MONTH_AND_DAY_VIEW);
    }
@@ -311,9 +288,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i

        // Position the Year View at the correct location
        if (viewIndices[YEAR_INDEX] == 0) {
            mDateLayout.addView(mHeaderYearTextView, 0);
        } else {
            mDateLayout.addView(mHeaderYearTextView, 1);
        } else {
            mDateLayout.addView(mHeaderYearTextView, 2);
        }

        // Position Day and Month Views
@@ -544,21 +521,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
        return false;
    }

    @Override
    public void setShowDoneButton(boolean showDoneButton) {
        mShowDoneButton = showDoneButton;
        updateDoneButtonVisibility();
    }

    private void updateDoneButtonVisibility() {
        mLayoutButtons.setVisibility(mShowDoneButton ? View.VISIBLE : View.GONE);
    }

    @Override
    public void setDismissCallback(DatePicker.DatePickerDismissCallback callback) {
        mDismissCallback = callback;
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        mYearFormat = new SimpleDateFormat("y", newConfig.locale);
@@ -640,7 +602,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
        updatePickers();
        setCurrentView(MONTH_AND_DAY_VIEW);
        updateDisplay(true);
        updateDoneButtonEnableState();
    }

    // If the newly selected month / year does not contain the currently selected day number,
@@ -684,16 +645,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
        mCurrentDate.set(Calendar.DAY_OF_MONTH, day);
        updatePickers();
        updateDisplay(true);
        updateDoneButtonEnableState();
    }

    private void updateDoneButtonEnableState() {
        if (mShowDoneButton) {
            final boolean enabled = mCurrentDate.equals(mMinDate) ||
                    mCurrentDate.equals(mMaxDate) ||
                    (mCurrentDate.after(mMinDate) && mCurrentDate.before(mMaxDate));
            mDoneButton.setEnabled(enabled);
        }
    }

    private void updatePickers() {
+5 −2
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
    private static final int ALPHA_TRANSPARENT = 0;

    // Alpha level of color for selector.
    private static final int ALPHA_SELECTOR = 255; // was 51
    private static final int ALPHA_SELECTOR = 60; // was 51

    // Alpha level of color for selected circle.
    private static final int ALPHA_AMPM_SELECTED = ALPHA_SELECTOR;
@@ -337,6 +337,9 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
        // list doesn't have a state for selected, we'll use this color.
        final int amPmSelectedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor,
                res.getColor(R.color.timepicker_default_ampm_selected_background_color_material));
        amPmBackgroundColor = ColorStateList.addFirstIfMissing(
                amPmBackgroundColor, R.attr.state_selected, amPmSelectedColor);

        mAmPmSelectedColor = amPmBackgroundColor.getColorForState(
                STATE_SET_SELECTED, amPmSelectedColor);
        mAmPmUnselectedColor = amPmBackgroundColor.getDefaultColor();
Loading