在 Android 上使用本机 date/time 选择器时的布局挑战 5.1.x

Layout challenge when using native date/time picker on Android 5.1.x

我这里有一种"catch 22"情况。

我正在使用 Appcelerator Titatium SDK 5.1.2。在 Android 5.1.x 上,屏幕尺寸非常小,我找不到使用本机选择器正确 select 日期和时间的解决方案。我需要添加一个 <ScrollView> 以允许用户移动内容以使选择器完全可见。但是,在 Android 5.1.x 上执行此操作时,用户无法在日期选择器中滚动回前几个月....将其更改为 <View> 控件会使日期选择器的行为如下预期的。但是,如果选择器的一部分在可见区域之外,则用户没有机会从那里 select 值...

我创建了一个简单的例子来说明这一点。在 <ScrollView> 而不是 <View> 中评论相同的 id 以查看差异:

查看:

<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>

风格:

".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"
}

控制器:

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();

对于冗长的代码感到抱歉(无法真正缩短它)- 但它是一个 有效 示例。

在 Android 的其他版本上,<ScrollView> 并不优先于本机日期选择器 - 因此允许用户轻松地重新定位整个内容以查看选择器,然后 select 日期。

我已经在 Genymotion VM 运行 API 22 中使用 480 x 800 (240 dpi) 的屏幕尺寸验证了上述应用程序。它很好地说明了问题:-)

知道如何为 Android 5.1.x 做这件事吗?

提前致谢!

/约翰

您尝试过在 ScrollView 上设置 canCancelEvents: false 吗?默认情况下,ScrollView 会对所有事件做出反应,甚至是其子项的事件。

嵌套的可滚​​动 UI 组件很容易出问题。在这种情况下,您可能希望改用 dialog