Search code examples
androidtableviewappceleratortitanium-mobiletitanium-alloy

How to fix touch events and images disappearing in a TableView using Titanium Alloy in Android?


I'm working with Titanium SDK 3.1.3 and deploying an Alloy project to Android. I have a Window in a xml file called UserProfile like this:

<Alloy>
    <Window class="container">

        <View id="MainHolder">

            <View id="MainHolderTitleView">

                <View id="CloseButton">
                    <Label id="CloseButtonLabel"></Label>
                </View>
                <Label id="MainHolderTitleLabel"></Label>
                <View id="CreatePostButton">
                    <ImageView id="CreatePostButtonImage" image="/images/menu_options.png"></ImageView>
                </View>
            </View>

            <View id="AppSettings">

                <View id="AppOptionButtons">
                    <View id="MenuOption1" class="optionButton">
                        <Label id="Option1Label" class="optionButtonLabel"></Label>
                    </View>
                    <View id="MenuOption2" class="optionButton">
                        <Label id="Option2Label" class="optionButtonLabel"></Label>
                    </View>
                    <View id="MenuOption3" class="optionButton">
                        <Label id="Option3Label" class="optionButtonLabel"></Label>
                    </View>

                </View>

            </View>

            <View id="MainHolderWorkspace">
                <TableView id="AppTableView"></TableView>
                <ActivityIndicator id="activityIndicator"/>
            </View>

        </View>

    </Window>
</Alloy>

the tss looks like this:

".container" : {
    backgroundColor:"#b1b1b1",
    width:Ti.UI.FILL, 
    height:Ti.UI.FILL, 
    top:0, 
    left:0,
    navBarHidden : true
}, 
"#MenuAppsButton":{
    height:'20dp'
},
"#MainHolderTitleLabel":{
    // width : '250dp',
    left : '60dp',
    right : '60dp',
    height : '25dp',
    color:"#FFFFFF", 
    textAlign : 'center',
    font:{fontSize:'12dp'},
    ellipsize : true
},
"#CreatePostButton":{
    right:0, 
    width:'44dp', 
    height:'44dp', 
    backgroundColor:"#242424"
},
"#CreatePostButtonImage":{
    height:'20dp'
},
".optionButton":
{
    width:Ti.UI.FILL, 
    height:'44dp', 
    backgroundColor:"#f0f0f0",
    top:1
}, 
".optionButtonLabel":
{
    color:"#363636", 
    font:{
        fontSize:'12dp', 
        fontWeight:"bold"
    }, 
    height:Ti.UI.SIZE, 
    width:Ti.UI.SIZE

},
"#AppOptionButtons":
{
    width:Ti.UI.FILL, 
    height:Ti.UI.SIZE,
    backgroundColor:"transparent", 
    layout:"vertical"

},
"#AppSettings":
{
    top:'44dp', 
    left:0, 
    backgroundColor:"transparent", 
    width:Ti.UI.FILL, 
    height:Ti.UI.FILL, 
    layout:"vertical"
},
"#MenuHolder":
{
    width:Ti.UI.FILL, 
    height:Ti.UI.FILL,
    top:0, 
    left:"-320dp", 
    zIndex:0, 
    backgroundColor:"#363636"
},
"#MainHolder":{
    width:Ti.UI.FILL, 
    height:Ti.UI.FILL,
    top:0, 
    left:0, 
    zIndex:1
},
"#MainHolderTitleView":
{
    left:0, 
    right:0, 
    height:'44dp', 
    top:0,
    backgroundColor:"#363636", 
    zIndex:0
}, 
"#MainHolderTitleDraggingView":
{
    left:0, 
    width:'44dp', 
    height:'44dp', 
    top:0,
    backgroundColor:"transparent", 
    zIndex:2
},
"#MainHolderWorkspace":
{
    top:'44dp', 
    bottom:0, 
    left:0,
    backgroundColor:"#b1b1b1",
    zIndex:2
}, 
"#MainHolderTitleViewMenuButton":
{
    width:'50dp', 
    height:'44dp', 
    left:0, 
    top:0, 
    backgroundColor:"#242424"
}, 
"#CloseButton":
{
    width:'60dp', 
    height:Ti.UI.FILL, 
    backgroundColor:"#232323", 
    left:0
},
"#CloseButtonLabel":
{
    color:"#FFFFFF", 
    font:{fontSize:'13dp'},
    text : L('close_string')
}, 
"#AppTableView":
{
    width:Ti.UI.FILL, 
    height:Ti.UI.SIZE,
    top:0,
    left:0, 
    showVerticalScrollIndicator:true, 
    backgroundGradient : {
            type : 'linear',
            colors :  [
                {
                    color : '#e7e7e7',
                    position : 0.0
                },
                {
                    color : '#d8d8d8',
                    position : 1.0
                }
            ]

    },
    separatorColor : 'transparent'
},
"#activityIndicator":
{
    width:Ti.UI.FILL
}

In my tableview I'm adding 2 rows with images and 20 rows more with info obtained from a web service, this rows contain images too, at the start. Every time the user clicks a load more view, 20 rows more are added with info obtained from the web service.

Each row I add is composed of different views, depending on the content received from the web service, the only constant being a view in a xml called Stream_Item that looks like this:

<Alloy>
    <View id="ContainerView" class="container">

        <View id="GlobalWorkspace">


            <View id="Workspace">

                <View id="WorkspacePersonalInfo">
                    <View id="WorkspaceProfileImage">
                        <ImageView id="WorkspaceProfileImageView" image="/images/emptyPicture.png"></ImageView>
                    </View>
                    <View id="WorkspaceNameAndDateHolder">
                        <View id="WorkspaceNameTargetHolder">
                            <Label id="WorkspaceNameLastName"></Label>
                            <ImageView id="TargetImageView"></ImageView>
                            <Label id="WorkspaceTarget"></Label>
                        </View>
                        <Label id="WorkspaceDate"></Label>
                    </View>
                </View>


                <View id="WorkspaceContent"></View>
            </View>

        </View>

        <View id="WorkspaceDecorator"></View>
    </View>
</Alloy>

EDIT 1 And the code for this controller look like this: //get arguments passed to controller var args = arguments[0] || {}; //load script to handle image cache var cacheImage = require('cacheImage'); //is this a simple or complex entry var simple = args.simple || 0;

//set values for this controller
$.WorkspaceNameLastName.text = args.user.name;
var created_at = args.created_at;
created_at = created_at.substring(0, created_at.indexOf('T'));
created_at = created_at.replace('-', '/');
created_at = created_at.replace('-', '/');
$.WorkspaceDate.text = created_at;
try
{
    cacheImage('cachedImages_UserImages_Thumb', args.user.avatar.thumb, $.WorkspaceProfileImageView, null);
}
catch(error)
{
    Ti.API.info(error);
}
//set correct width based on device screen
var workspaceWidth = Ti.Platform.displayCaps.platformWidth;
$.GlobalWorkspace.setWidth(workspaceWidth);
//if user pic is tapped, open profile
$.WorkspaceProfileImage.addEventListener("singletap", function(e) {
    Alloy.createController("UserProfile", {
        peopleObject:args.user
    });
});
//arguments for like/dislike controller
var likenessArgs = {
    data : args
};
//arguments for text controller
var textArgs = {
    id : args.id,
    text : L(args.action),
    simple : true,
    index : args.index,
    simple : simple
};
//function to get the correct name of the class according to internationalization
function getClass(e)
{
    switch(e)
    {
        case "Group":
            return L('group_string');
        case "Event":
            return L('event_string');
        case "Todo":
            return L('todo_list_string');
        case "TodoTask":
            return L('todo_string');
        case "Album":
            return L('album_string');
        case "AlbumPhoto":
            return L('photo_string');
        case "User":
            return L('user_string');

    }
}
//hide target user arrow image 
$.TargetImageView.setOpacity(0);
//function to open viewer according to class
function openViewer(type)
{
    switch(type)
    {
        case "User":
            Alloy.createController("UserProfile", {
                peopleObject : {
                    veramiko_id : args.target.veramiko_id
                }
            }); 
        break;
        case "Event":
            Alloy.createController("Event_Details", {
                evento : {
                    veramiko_id : args.target.veramiko_id
                }
            }); 
        break;
        case "Group":
            Alloy.createController("Group_Profile", {
                groupItem : {
                    veramiko_id : args.target.veramiko_id
                }
            }); 
        break;
        case "Album":
            Alloy.createController("PhotoAlbumViewer", {
                album : {
                    veramiko_id : args.target.veramiko_id
                }
            }); 
        break;
    }
}
//arguments for comments controller
var wall_posts_count = {};
wall_posts_count.wall_posts_count = parseInt(args.wall_posts_count);
wall_posts_count.id = args.id;
//variable to store attachment controller
var streamItemImageView;
//check if data received has a secondary target
if ( typeof args.secondary_target !== 'undefined') 
{
    //check if secondary target has a comment
    if ( typeof args.secondary_target.comment !== 'undefined') 
    {
            //construct the comment according to the context
        var comment = args.secondary_target.comment;
        textArgs.text = comment;
            //check if user target is the same as logged user
        if(args.target.veramiko_id !== Ti.App.Properties.getObject('loggedUser').user.veramiko_id)
        {
            if(args.target.veramiko_id !== args.user.veramiko_id)
            {
                            //since there's a target display the necessary info for this target and the arrow target image
                $.WorkspaceTarget.text = args.target.name;
                $.WorkspaceTarget.width = $.WorkspaceTarget.toImage().width;
                $.TargetImageView.image = Ti.Filesystem.resourcesDirectory + 'images/targetArrow.png';
                $.TargetImageView.setOpacity(1);
                $.WorkspaceTarget.addEventListener('singletap', function(e){
                    openViewer(args.target.class);
                });
            }
        }
    }
    //check if there's an attachment
    if ( typeof args.secondary_target.attachment !== 'undefined') 
    {
            //create attachment controller
        streamItemImageView = Alloy.createController("Stream_Item_Image", {
            attachment : args.secondary_target.attachment,
            comment : args.secondary_target.comment,
            created_at : args.created_at,
            updated_at : args.updated_at
        }).getView();
        $.WorkspaceContent.add(streamItemImageView);
    }
}
// since there's no secondary target, it's a text post
else
{
    var comment = args.target.name;
    comment = "\"" + comment + "\"";
    textArgs.text = textArgs.text + ': ' + comment;
}
// create like/dislike controller
var streamItemLikeDislikeView = Alloy.createController("Stream_Item_LikeDislike", likenessArgs);
// variable for user mentions in post
var streamItemMentions;
// check if there's any user mentions in this post
var mentionsArgs;
if(typeof args.mentions !== 'undefined')
{
    // since there're user mentions, create the controller and the arguments for it
    mentionsArgs = {
        mentions : args.mentions
    };
    streamItemMentions = Alloy.createController("Stream_Item_Mentions", mentionsArgs);  
}
// create comments controller
var streamItemCommentsView = Alloy.createController("Stream_Item_Comments", wall_posts_count);
// callback object with functions that update the comments count
var callbackObject = {};
callbackObject.increaseNumberOfComments = streamItemCommentsView.increaseNumberOfComments;
textArgs.callbacks = callbackObject;
// create text controller
var streamItemTextView = Alloy.createController("Stream_Item_Text", textArgs).getView();
$.WorkspaceContent.add(streamItemTextView);
//if there're user mentions, add the controller
if(typeof args.mentions !== 'undefined')
{
    $.WorkspaceContent.add(streamItemMentions.getView());
}
// if it's a simple entry add the comments controller
if(args.simple)
{
    $.WorkspaceContent.add(streamItemCommentsView.getView());
}
$.WorkspaceContent.add(streamItemLikeDislikeView.getView());

END EDIT 1

In the view WorkspaceContent more views are added depending if comments had been made or images had been attached to the entry.

EDIT 2 Also every Stream_Item controller contains a text controller and a like/dislike controller, depending on whether the entry has user mentions, an attachment and/or is a simple entry, additional controllers are added.

I must make clear that this Stream_Item controller and its added controllers work just fine in other Windows, but on this specific kind of Window it gets buggy.

END EDIT 2

The problem I'm having in Android is that the touch events on the views contained on the rows are blocked for some reason, I have known about the performance issues on Android when dealing with TableViews and after testing in several devices, that unless the element that has the touch event has finished rendering the user will be allowed to interact with the views with touch events. This is only a guess but I think is somehow accurate to my problem. Another problem I experience is the images contained in the table rows sometimes disappear while scrolling or interacting with other elements of the table.

I switched from using ScrollView mainly because the methods for adding elements to a table view are way more practical than the ones found in ScrollView (i.e. the ability to insert rows at an specified position and such behaviour not found in ScrollView). I tried List View but the implementation in Alloy is so confusing and the documentation isn't clear on how to change the contents dynamically using other Alloy views.

Is there a way to workaround these issues? How can I make the images to stop disappearing? How can I bypass this touch block?


Solution

  • I have very bad experience of this(disappearing) issue too. Since tableView is not a native element for Android as it is for iPhone that is why Android is unable to handle the TableView perfectly and creating these type of issues. I have experienced this issue many times. And I would suggest you to use ListView instead of TableView for android. Only this way I can say you will be able to avoid these issues. And for the touch problem. I think you need to use TouchEvents on the Elements directly instead of Views. How you are using the TouchEvents for Views?