开发者

Layout for TimePicker-based DialogPreference

I'm writing an app that requires a TimePicker-based preference (two, actually) and I swiped some (Apache-licensed) code from the Android Programming Tutorials book. I've modified it to work a little better, but there's one thing puzzling me.

I haven't tried it on an actual device yet but on emulators it now takes into account system settings for 24-hour time display, clears focus on the TimePicker dialog so that the time is read even when edited manually instead of using the arrow keys, and - the problematic part - displays the selected time in the PreferenceScreen.

The problem is that I've tried this in emulators for API levels 3, 4, 7 and 10 and it only works completely correctly in 10. In the emulators for the earlier versions, if I put a checkbox in the PreferenceScreen with two TimePreferences, the TextViews in the two TimePreferences switch positions every time the checkbox is toggled. So if I click the checkbox the first time the app starts, the start time looks like 10pm and the stop time looks like 8am (even though when I click on the preference, the appropriate time shows up in the TimePicker).

Is there a better way to do this? Preferably an elegant way so that the entire class is self-contained and I don't need to create layouts in XML files that are applicable only to the class? Or is there a way to work around/fix this behavior? I'd also like to avoid using setSummary so that there's still opportunity to add a summary to the preference. Most of my experimentation has been inside onCreateView with the layout code but I can't get anything to work completely right. This seems to happen whether I use RelativeLayout or LinearLayout.

The full code is as follows:

package test.android.testproject2;

//imports...

public class TimePreference extends DialogPreference {
    private int lastHour=0;
    private int lastMinute=0;
    private boolean is24HourFormat;
    private TimePicker picker=null;
    private TextView timeDisplay;

    public TimePreference(Context ctxt) {
        this(ctxt, null);
    }

    public TimePreference(Context ctxt, AttributeSet attrs) {
        this(ctxt, attrs, 0);
    }

    public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
        super(ctxt, attrs, defStyle);

        is24HourFormat = DateFormat.is24HourFormat(ctxt);
        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    public String toString() {
        if(is24HourFormat) {
            return ((lastHour < 10) ? "0" : "")
                    + Integer.toString(lastHour)
                    + ":" + ((lastMinute < 10) ? "0" : "")
                    + Integer.toString(lastMinute);
        } else {
            int myHour = lastHour % 12;
            return ((myHour == 0) ? "12" : ((myHour < 10) ? "0" : "") + Integer.toString(myHour))
                    + ":" + ((lastMinute < 10) ? "0" : "") 
                    + Integer.toString(lastMinute) 
                    + ((lastHour >= 12) ? " PM" : " AM");
        }
    }

    @Override
    protected View onCreateDialogView() {
        picker=new TimePicker(getContext().getApplicationContext());
        return(picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);
        picker.setIs24HourView(is24HourFormat);
        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

    @Override
    protected View onCreateView (ViewGroup parent) {
         View prefView = super.onCreateView(parent);
         LinearLayout layout = new LinearLayout(parent.getContext());
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT, 2);
         layout.addView(prefView, lp);
         timeDisplay = new TextView(parent.getContext());
         timeDisplay.setGravity(Gravity.BOTTOM | Gravity.RIGHT);
         timeDisplay.setText(toString());
         LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT, 1);
         layout.addView(timeDisplay, lp2);
         return layout;
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            picker.clearFocus();
            lastHour=picker.getCurrentHour();
            lastMinute=picker.getCurrentMinute();

            String time=String.valueOf(lastHour)+":"+String.valueOf(lastMinute);

            if (callChangeListener(time)) {
                persistString(time);
                timeDisplay.setText(toString());
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return(a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        String time=null;

        if (restoreValue) {
开发者_高级运维            if (defaultValue==null) {
                time=getPersistedString("00:00");
            }
            else {
                time=getPersistedString(defaultValue.toString());
            }
        }
        else {
            if (defaultValue==null) {
                time="00:00";
            }
            else {
                time=defaultValue.toString();
            }
            if (shouldPersist()) {
                persistString(time);
            }
        }

        String[] timeParts=time.split(":");
        lastHour=Integer.parseInt(timeParts[0]);
        lastMinute=Integer.parseInt(timeParts[1]);;
    }
}

An example preferences layout is below:

<?xml version="1.0" encoding="UTF-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="@string/preferences_time_title">
        <test.android.testproject2.TimePreference
            android:key="preferences_start_time"
            android:showDefault="true"
            android:defaultValue="08:00"
            android:summary="When to start"
            android:title="@string/preferences_start_time"/>
        <test.android.testproject2.TimePreference
            android:key="preferences_stop_time"
            android:showDefault="true"
            android:defaultValue="22:00"
            android:summary="When to stop"
            android:title="@string/preferences_stop_time"/>
    </PreferenceCategory>
    <PreferenceCategory android:title="@string/preferences_options_title">
        <CheckBoxPreference
            android:key="preferences_enabled"
            android:defaultValue="false"
            android:title="@string/preferences_enabled"/>
    </PreferenceCategory>
</PreferenceScreen>

Screenshots:

Layout for TimePicker-based DialogPreference

Layout for TimePicker-based DialogPreference

This happens on emulators for APIs 3, 4 and 7. I've also tried it on 10 and it works correctly.

Update: It works correctly on an emulator with API 8 as well (2.2/Froyo).

Update 2: I've rewritten the onCreateView method as follows. It still fails the same way, but it's more efficient to render and I believe it behaves closer to the requirements specified in the Android documentation.

    @Override
    protected View onCreateView (ViewGroup parent) {
         ViewGroup prefView = (ViewGroup) super.onCreateView(parent);
         View widgetLayout;
         int childCounter = 0;
         do {
             widgetLayout = prefView.getChildAt(childCounter);
             childCounter++;
         } while (widgetLayout.getId() != android.R.id.widget_frame); 
         timeDisplay = new TextView(widgetLayout.getContext());
         timeDisplay.setText(toString());
         ((ViewGroup) widgetLayout).addView(timeDisplay);
         return prefView;
    }

My next step is to start some logging in onCreateView, onBindView and getView and see how they're called and what's going on inside the views. If anyone comes up with any ideas in the meantime, please let me know!


I've figured it out and fixed it. It now works in every level of the API from Cupcake (1.5 - level 3) to Gingerbread (2.3.3 - level 10). Code is below. Please note that the original code is Apache-licensed as specified in the question. I hereby dedicate all of my modifications to the public domain.

package test.android.testproject2;

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.TimePicker;

public class TimePreference extends DialogPreference {
    protected int lastHour=0;
    protected int lastMinute=0;
    protected boolean is24HourFormat;
    protected TimePicker picker=null;
    protected TextView timeDisplay;

    public TimePreference(Context ctxt) {
        this(ctxt, null);
    }

    public TimePreference(Context ctxt, AttributeSet attrs) {
        this(ctxt, attrs, 0);
    }

    public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
        super(ctxt, attrs, defStyle);

        is24HourFormat = DateFormat.is24HourFormat(ctxt);
        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    public String toString() {
        if(is24HourFormat) {
            return ((lastHour < 10) ? "0" : "")
                    + Integer.toString(lastHour)
                    + ":" + ((lastMinute < 10) ? "0" : "")
                    + Integer.toString(lastMinute);
        } else {
            int myHour = lastHour % 12;
            return ((myHour == 0) ? "12" : ((myHour < 10) ? "0" : "") + Integer.toString(myHour))
                    + ":" + ((lastMinute < 10) ? "0" : "") 
                    + Integer.toString(lastMinute) 
                    + ((lastHour >= 12) ? " PM" : " AM");
        }
    }

    @Override
    protected View onCreateDialogView() {
        picker=new TimePicker(getContext().getApplicationContext());
        return(picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);
        picker.setIs24HourView(is24HourFormat);
        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

    @Override
    public void onBindView(View view) {
        View widgetLayout;
        int childCounter = 0;
        do {
            widgetLayout = ((ViewGroup) view).getChildAt(childCounter);
            childCounter++;
        } while (widgetLayout.getId() != android.R.id.widget_frame); 
        ((ViewGroup) widgetLayout).removeAllViews();
        timeDisplay = new TextView(widgetLayout.getContext());
        timeDisplay.setText(toString());
        ((ViewGroup) widgetLayout).addView(timeDisplay);
        super.onBindView(view);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            picker.clearFocus();
            lastHour=picker.getCurrentHour();
            lastMinute=picker.getCurrentMinute();

            String time=String.valueOf(lastHour)+":"+String.valueOf(lastMinute);

            if (callChangeListener(time)) {
                persistString(time);
                timeDisplay.setText(toString());
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return(a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        String time=null;

        if (restoreValue) {
            if (defaultValue==null) {
                time=getPersistedString("00:00");
            }
            else {
                time=getPersistedString(defaultValue.toString());
            }
        }
        else {
            if (defaultValue==null) {
                time="00:00";
            }
            else {
                time=defaultValue.toString();
            }
            if (shouldPersist()) {
                persistString(time);
            }
        }

        String[] timeParts=time.split(":");
        lastHour=Integer.parseInt(timeParts[0]);
        lastMinute=Integer.parseInt(timeParts[1]);;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜