Layout challenge when using native date/time picke

2019-09-13 05:41发布

问题:

I have a kind of "catch 22" situation here.

I am using Appcelerator Titatium SDK 5.1.2. On Android 5.1.x with a very small screen size I cannot find a solution to correctly select date and time using the native pickers. I need to add a <ScrollView> to allow the user to move the contents to make the picker entirely visible. However, when doing this on Android 5.1.x the user cannot scroll back to previous months in the date picker.... Changing it to a <View> control makes the date picker behave as expected. However, if part of the picker is outside of the visible area the user has no chance to select values from there...

I have created a simple example to illustrate this. Comment in <ScrollView> instead of <View> with same id to see difference:

View:

<Alloy>
    <Window class="container">
        <View id="form" onClick="clickHandler">
        <!-- 
        <ScrollView id="form" onClick="clickHandler">
        -->
            <View id="formRow">
                <Label id="title">Picker demo</Label>
            </View>
            <View id="formRow">
                <Label id="label">Date</Label>
                <TextField id="startDate" bubbleParent="true" editable="false"></TextField>
            </View>
            <View id="formRow">
                <Label id="label">Time</Label>
                <TextField id="startTime" bubbleParent="true" editable="false"></TextField>
            </View>
        <!-- 
        </ScrollView>
        -->
        </View>
    </Window>
</Alloy>

Style:

".container": {
    top: 20,
    backgroundColor:"#fa0",
    orientationModes: [Ti.UI.PORTRAIT]
}
"Label": {
    width: Ti.UI.SIZE,
    height: Ti.UI.SIZE,
    backgroundColor: 'transparent',
    left:10, 
    color: "#000"
}

"#title": { top:15, 
    font: {
        fontSize: '25dp',
        fontStyle: 'bold'
    }
}
"#label": { top:0, 
    font: {
        fontSize: '18dp',
        fontStyle: 'bold'
    }
}
"TextField": { font: {
        fontSize: '18dp',
        fontStyle: 'normal'
   },
    backgroundColor:'orange'
}
"#formRow":{
    top:7,
    height:Ti.UI.SIZE,
    width:Ti.UI.FILL
}

"#startDate":{
    top:0,
    width:150,
    right:10,
}
"#startTime":{
    top:0,
    width:70,
    right:10
}
"#form":{
    showVerticalScrollIndicator:"true",
    layout:"vertical"
}

Controller:

var Moment = require('alloy/moment');

function clickHandler(e){
    if(e && e.source){
        console.log("clickHandler. id="+e.source.id);
        if(e.source.id === 'startDate'){
            openDatePicker(e);
        } else if(e.source.id === 'startTime'){
            openTimePicker(e);
        }
    }
}

// Date/time utils:
function getAsDate(date) {
    // Return as Date object
    var dt = null;
    if(typeof date === 'number') {
        dt = new Date(date);
    } else if(date instanceof Date) {
        dt = date;
    }
    return dt;
};
function getDMY(date) {
    var dt = getAsDate(date);
    if(dt) {
        return Moment(dt).format('DD-MM-YYYY');
    }
    return null;
}

function getHMM(date) {
    // Returns format: H:mm
    var dt = getAsDate(date);
    if(dt) {
        return Moment(dt).format('H:mm');
    }
    return null;
}
function fromDMY(dt){
    var date = Moment(dt, "DD-MM-YYYY");
    if(date){
        date = new Date(date);
    }
    return date;
}

function fromHMM(time){
    var datetime = Moment(time, "H:mm");
    if(datetime) {
        return new Date(datetime);
    }
    return null;
}

function openDatePicker(){
     // Inner helper class to clean up and remove picker items
    function cleanup(){
        console.log("openDatePicker.cleanup...");
        pickerOpen = null;
        $.startDate.value = getDMY(picker.value);
        $.formRow.remove(picker);
        $.startDate.bubbleParent = true;
        $.form.removeEventListener('click',cleanup);
    }

    var v = $.startDate.value;
    if(v && fromDMY(v)) {
        v = fromDMY(v);
        console.debug("startDate.value=" + $.startDate.value + " --> v=" + v + " - type: " + typeof v);
    }else{
        v = new Date();
    }
    var picker = Ti.UI.createPicker({
          type:Ti.UI.PICKER_TYPE_DATE,
          maxDate:new Date(),
          top:35,
          value:v
    });
    $.formRow.add(picker);
    $.startDate.bubbleParent = false;
    $.form.addEventListener('click',cleanup);
}

function openTimePicker(){
     // Inner helper class to clean up and remove picker items
    function cleanup(){
        console.log("openTimePicker.cleanup...");
        pickerOpen = null;
        $.startTime.value = getHMM(picker.value);
        $.formRow.remove(picker);
        $.startTime.bubbleParent = true;
        $.form.removeEventListener('click',cleanup);
    }

    var v = $.startTime.value;
    if(v && fromHMM(v)) {
        v = fromHMM(v);
        console.debug("startTime.value=" + $.startTime.value + " --> v=" + v + " - type: " + typeof v);
    }else{
        v = new Date();
    }
    var picker = Ti.UI.createPicker({
          type:Ti.UI.PICKER_TYPE_TIME,
          format24:true,
          minuteInterval:5,
          top:35,
          value:v
    });
    $.formRow.add(picker);
    $.startTime.bubbleParent = false;
    $.form.addEventListener('click',cleanup);
}

$.index.open();

Sorry for the lengthy code (couldn't really make it shorter) - but it is a working example.

On other versions of Android the <ScrollView> does not take precedence over the native date picker - and thus allows the user to easily reposition the entire contents to see the picker and then select the dates.

I have verified the above app in a Genymotion VM running API 22 using a screen size of 480 x 800 (240 dpi). It illustrates the problem quite well :-)

Any ideas how to do this for Android 5.1.x?

Thanks in advance!

/John

回答1:

Nested scrollable UI components is a recipe for problems. In this case you might want to use the dialog instead.



回答2:

Have you tried setting canCancelEvents: false on the ScrollView? At its default of true, the ScrollView reacts to all events even those of its children.