Show Online Video Duration In GridView - android

I am using an FTP Client to get videos from a server and show them on a gridview. I would like to display duration of each gridview item (videos) as well.
I have tried using this code outside of the gridview adapter:
for (FTPFile file : files) {
if (file.isFile())
{
//GetDuration
try{
//NOTE: must not use https
mmr = new FFmpegMediaMetadataRetriever();
mmr.setDataSource(myDomain+skyVideos+"/"+username+"/"+file.getName());
long duration =Long.parseLong(mmr.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_DURATION));
duration=duration/1000;
long minute=duration/(60);
long second=duration-(minute*60);
mmr.release();
strDuration = String.format("%02d:%02d" , minute, second);
//ALL VIEWS
for (int j = 0; j < gv.getChildCount(); j++) {
View child = gv.getChildAt(j);
holder.tvDuration = child.findViewById(R.id.tvDuration);
runOnUiThread(new Runnable() {
#Override
public void run() {
holder.tvDuration.setText(strDuration);
}
});
}
arrListStr_Duration.add(strDuration);
stringArr_Duration = new String[arrListStr_Duration.size()];
stringArr_Duration = arrListStr_Duration.toArray(stringArr_Duration);
} catch (IllegalStateException e) {
//
}catch (IllegalArgumentException e)
{
//
}
}
//SORT BY TIMESTAMP
Arrays.sort(files, Comparator.comparing((FTPFile remoteFile) -> remoteFile.getTimestamp()).reversed());
}
client.disconnect();
This works fine, even though it sets duration (holder.tvDuration.setText(strDuration);) of videos one after the other and not all at the same time (because of how the for loop works?) even though I'm running this method in a background: (I can live with this though)
new Thread(new Runnable() {
#Override
public void run() {
//code in background here
getFTP_Duration();
getFTP_SizeArray();
}
}).start();
Anyway this all fine until the user starts scrolling before the arrListStr_Duration is fully populated then the duration will be set on the wrong video because the views get recycled.
Now in the GridView adapter I have tried the following in the View getView method:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
holder = new Holder();
if (convertView == null)
{
convertView = inflater.inflate(R.layout.activity_video_gridview_single_checkbox, null);
}
holder.ivImage = convertView.findViewById(R.id.ivImage);
holder.tvInvisibleDate = convertView.findViewById(R.id.tvInvisibleDate);
holder.ivCheckbox = convertView.findViewById(R.id.ivCheckbox);
holder.tvDuration = convertView.findViewById(R.id.tvDuration);
holder.ibDots = convertView.findViewById(R.id.ibDots);
holder.mypopupWindow = null;
if (arrListStr_File.size() == arrListStr_Duration.size())
{
//Duration finished populating
holder.tvDuration.setText(stringArr_Duration[position]);
}
else
{
//user scrolling while duration is still being populated
try {
for (int i = 0; i < gv.getChildCount(); i++) {
if (arrListStr_Duration.size() != 0)
{
View child = gv.getChildAt(i);
holder.tvDuration = child.findViewById(R.id.tvDuration);
try {
holder.tvDuration.setText(stringArr_Duration[i]);
}catch (IndexOutOfBoundsException e)
{
//
}
}
}
}catch (NullPointerException e)
{
//
}
}
...
This will also mix up the duration and videos because gv.getChildCount() only returns count of what can be seen on screen and this count is reset when user scrolls because of recycling. When the user scrolls after the duration has been fully populated then there are no issues but of cause this happens in the background and user should be able to scroll whenever they want to and still have the correct duration set on the correct videos, how can I achieve this? I thought of not using recycling at all but I understand that this is bad for performance. Hope I made sense.

Documentation of adapter's getView()
* #param position The position of the item within the adapter's data set of the item whose view we want.
* #return A View corresponding to the data at the specified position.
View getView(int position, View convertView, ViewGroup parent);
When the user scrolls after the duration has been fully populated then there are no issues.>
This is because you are correctly using position in this case, whereas in other case you are using child index, that's why GridView is mixing up your videos.
Your entire getView() can be simplified by creating a method to get video duration.
private String getVideoDuration(int index){
if(index <= stringArr_Duration.length)
return stringArr_Duration[index];
return null; // can return empty string also here.
}
Then call it from getView() and pass #param : position to it, not the child index.

Related

ListItemClick not working in ListFragment with ParseQueryAdapter

My code involves showing data retrieved from a parse backend using a parsequeryadapter. Based on the text retrieved, my code displays some images in the listview which are basically buttons with custom backgrounds and some text.
The problem is that the onListItemClick method does not seem to be called at all. (It might work if it is called)
Construct of code is like so:
NavActivity contains a fragment which contains a child listfragment defined in the XML.
This is the getItemView method in my ParseQueryAdapter
#Override
public View getItemView(ParseObject object, View v, ViewGroup parent) {
if (v == null) {
v = View.inflate(context, R.layout.post_item, null);
}
super.getItemView(object, v, parent);
//find the elements here and set the value from the ParseObject returned
TextView1 = (TextView) v.findViewById(R.id.NumberTxtView);
Holder = (LinearLayout) v.findViewById(R.id.feloniesList);
if (object != null) {
Holder.removeAllViewsInLayout();
TextView1.setText(object.getString("comment"));
JSONArray jsonArray = object.getJSONArray("incident");
for (int i = 0; i < jsonArray.length(); i++) {
//Create a new button
Button button = new Button(getContext());
String btnText = null;
int resId = 0;
try {
//Get the string from the JSON Array
String item = jsonArray.getString(i);
button.setFocusable(false);
button.setText(item);
} catch (JSONException e) {
e.printStackTrace();
}
Holder.addView(button, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
return v;
}
ListItemClick method
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
feedObject = feedAdapter.getItem(position);
mListener.onListItemClicked(feedObject.getObjectId());
}
I read here that making other components as non focusable, will solve the problem, but it is not helping. Any help would be appreciated
I found the answer, it seems focusable is not only true for buttons / checkboxes, but also elements like HorizontalScrollViewer. I had my linearlayout in which I was adding the buttons, enclosed in a horizontalscrollviewer. After I removed said element, click functions are working fine.

Update value on adapter

I wonder if there is any way to update an information of a single item within a listview. Basically I press the button inside the adapter and it makes a new request, the request will set this returns the value of the adapter. It is a system of "like".
I do not want to call the asynchronous method that gets the list, it takes much again. The code to get all the items in the database is this:
protected ArrayList<Feed> doInBackground(MyTaskParams... params) {
page = params[0].page;
mFilter = params[0].filter;
backgroundItems = new ArrayList<Feed>();
ParseQuery<ParseObject> query = ParseQuery.getQuery("FeedPost");
ParseObject parseObject;
try {
responseList = query.find();
for (int i = 0; i < responseList.size(); i++) {
parseObject = responseList.get(i);
backgroundItems.add(new Feed(parseObject.getObjectId(),
parseObject.getString("Title"),
parseObject.getString("Description"),
parseObject.getString("CompleteText"),
parseObject.getString("imageURL"),
parseObject.getString("Link_on_Site"),
parseObject.getNumber("like_count")));
}
} catch (ParseException e) {
exceptionToBeThrown = e;
}
return backgroundItems;
}
You didn't show your adapter code or your task for updating the like count in the database, so I'm going to assume you're using an AsyncTask. In the getView or bindView function is where you need to update it.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder;
if (convertView == null)
{
//inflate layout and initialize holder
}
else
{
holder = (ViewHolder)convertView.getTag();
}
//get item at position
MyItem item = getItem(position);
if (item != null)
{
holder.likeButton.setOnClickListener(
new OnClickListener()
{
#Override
public void onClick(View v)
{
//update database and set new value
IncrementLikeCountTask task = new IncrementLikeCountTask()
{
#Override
public void onPostExecute(int newValue)
{
item.setLikeCount(newValue);
notifyDataSetChanged();
}
}.execute(position);
}
}
);
}
return convertView;
}
So I assume you're persisting a list of Feed objects in your adapter.
What you need to do is after communicating with the server modify the like_count field of the appropriate Feed object in your adapter and call notifyDataSetChanged() on the adapter.
This will trigger a refresh of the ListView and render your list item with the updated value.

Android SDK Issue setting/resetting ListView item background

I wish to manually rearrange the order of a ListView. The way I (want to) achieve this is by tapping on the item to be moved, setting the background of that item to a different colour, storing its position (oldPosition) and then tapping on the item which it is to appear below and finally resetting the background of the original item location.
The code I use to do this is:-
List<String> catarray; // string array declared in main activity
ArrayAdapter<String> catadapter; // adapter for Spinner declared in main activity
ListView cats; // listview declared in list activity
ArrayAdapter<String> adapter; // adapter for listview declared in list activity
int oldPosition = -1;
cats.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (oldPosition < 0) {
oldPosition = position;
try {
dr = parent.getChildAt(position).getBackground();
parent.getChildAt(position).setBackgroundColor(Color.argb(255, 0, 153, 204));
}
catch (Exception e) {
}
}
else {
String item = PhotoActivity.catarray.remove(oldPosition);
PhotoActivity.catarray.add(position,item);
try {
parent.getChildAt(oldPosition).setBackground(dr);
}
catch (Exception e) {
}
oldPosition = -1;
changed = true;
PhotoActivity.catadapter.notifyDataSetChanged();
adapter.notifyDataSetChanged();
}
}
});
The problem that I have is that if the list is larger than the displayed view then both the item that I tap on gets the background changed but so does an additional item that is below the visible range.
So, for example, if the full list is, say, 12 items long of which the first 8 are being displayed, if I tap on the second item then both that item but also item 11 (ie the second item plus one below the visible range) is highlighted.
Why does this happen?
How can I either stop it or, if I can't do that, reset the incorrectly highlighted item given that it is not visible and therefore not accessible via parent.getChildAt ...
The only answer I've found is to add a getView method for the adapter, call super.getView and then change the background colour there.
Also, you need to call view.setBackgroundColor in the OnItemClickListener event to make sure that the background is changed at the point of clicking.
So, my code ends up as being:-
cats.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (oldPosition < 0) {
oldPosition = position;
try {
view.setBackgroundColor(Color.argb(255, 0, 153, 204));
}
catch (Exception e) {
}
}
else {
String item = PhotoActivity.catarray.remove(oldPosition);
PhotoActivity.catarray.add(position,item);
oldPosition = -1;
changed = true;
PhotoActivity.catadapter.notifyDataSetChanged();
adapter.notifyDataSetChanged();
}
}
});
adapter = new ArrayAdapter<String>(c, android.R.layout.simple_list_item_1, PhotoActivity.catarray) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
if (oldPosition> -1 && oldPosition==position) {
textView.setBackgroundColor(Color.argb(255, 0, 153, 204));
}
else {
textView.setBackgroundColor(Color.argb(0, 0, 0, 0));
}
return textView;
}
};
cats.setAdapter(adapter);
My thanks to Cameron Saul at SO answer here for pointing me in the right direction.

Refresh listView with multiple view types after delete/remove item

I have a ListView with a custom adapter that extends BaseAdapter and has 2 view types. When I run my adapter.removeRow(position) method, the data for the adapter is correctly updated, and the list reflects this, but the view types are not correctly updated. The Adapter is backed by
ArrayList<Map<String, String>> rows = new ArrayList<Map<String, String>>();
and I have a subset
List<Integer> flashSet = new ArrayList<Integer>();
which is a list of all the positions that are of ViewType 1 (as opposed to the standard view type 0).
Here is my adapter removeRow(position) method:
public void removeRow(int position) {
if (getItemViewType(position) == TYPE_FLASH) {
flashSet.remove(position);
}
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashPosition -= 1;
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
rows.remove(position);
notifyDataSetChanged();
}
Here is my getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FlashHolder flashHolder;
ClipHolder clipHolder;
int type = getItemViewType(position);
if (convertView == null) {
if (type == TYPE_CLIP) {
convertView = rowInflater.inflate(R.layout.clip_note_row_layout, null);
clipHolder = new ClipHolder();
flashHolder = null;
clipHolder.textView = (TextView)(convertView.findViewById(R.id.clip_text));
convertView.setTag(clipHolder);
} else {
convertView = rowInflater.inflate(R.layout.flash_row_layout, null);
clipHolder = null;
flashHolder = new FlashHolder();
flashHolder.front = (TextView)(convertView.findViewById(R.id.flash_text));
flashHolder.back = (TextView)(convertView.findViewById(R.id.clip_text));
convertView.setTag(flashHolder);
}
} else {
if (type == TYPE_CLIP) {
clipHolder = (ClipHolder)convertView.getTag();
flashHolder = null;
} else {
clipHolder = null;
flashHolder = (FlashHolder)convertView.getTag();
}
}
if (type == TYPE_CLIP) {
clipHolder.textView.setText(rows.get(position).get("clip"));
} else {
flashHolder.front.setText(rows.get(position).get("flash_text"));
flashHolder.back.setText(rows.get(position).get("clip"));
}
return convertView;
}
I know that I could create a new adapter, give it the updated ArrayList and call listView.setAdapter(adapter) but this seems total overkill when I'm simply trying to remove one item from a potentially long list. See pics for a before and after deleting:
Then I delete the first item. The word "which" was hidden behind the "Let's watch it" item and now the "inspired by…" item is hidden behind a blank item 3.
So, data is updating, view types aren't. Thanks for the help!
I figured it out. This will be useful to no one as I don't expect others to make the same mistake.
I naively thought that by doing this
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashPosition -= 1;
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
I was changing the actual value stored in the List<Integer> flashSet = new ArrayList<Integer>();
In fact, I need to do the following instead:
for (int flashPosition:flashSet) {
System.out.println(tag+"is "+flashPosition+" going to be moved?");
if (flashPosition > position) {
flashSet.remove((Object)flashPosition);
flashPosition -= 1;
flashSet.add(flashPosition);
System.out.println(tag+"Yes! It's been moved to "+flashPosition);
}
}
Try this, After delete or add item you need to call adapter refresh.
Youradapter.notifyDataSetChanged();
use yourlistview.invalidateViews()
instead of
notifyDataSetChanged();
it works for me.

Use AsyncTask to add items to list view individually

I am trying to add ListView items one by one. So if I have say -- 60 items -- the application would add the views to the list view one at a time -- thus showing the user that the application is loading more things.
This is my code:
try {
JSONArray j = getTaggsJSON();
Log.v(TAG, String.valueOf(j.length()));
a = new createSpecialAdapter(this, R.layout.individual_tagg_view,
R.layout.list_item, view, j);
ListView v = this.getListView();
v.setStackFromBottom(true);
setListAdapter(a);
new addViewsToList().execute(j);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
}
}
private class addViewsToList extends AsyncTask<JSONArray, View, List<View>> {
protected List<View> doInBackground(JSONArray... jsonArrays) {
List<View> v = new ArrayList<View>();
Log.v(TAG, String.valueOf(jsonArrays[0].length()));
for (int x = 0; x < jsonArrays[0].length(); x++) {
try {
Log.v(TAG, jsonArrays[0].getJSONObject(x).toString());
v.add(ViewAdapter.createTaggView(jsonArrays[0]
.getJSONObject(x), c));
} catch (JSONException e) {
e.printStackTrace();
}
publishProgress(v.get(x));
}
return v;
}
protected void onProgressUpdate(View... v) {
Log.v(TAG, "I'm updating my progress!");
a.notifyDataSetChanged();
}
protected void onPostExecute(List<View> views) {
Log.v(TAG, "i'm done!");
Log.v(TAG, String.valueOf(views.size()));
}
}
So, it looks like my code is printing out the views correctly, and putting them in the list correctly, however my activity displays nothing. What am I doing wrong? I thought setting the adapter before -- when there are no views in the list -- and then updating the adapter that the list was changed was the right way to go.... Obviously not.. Can anyone help me?
If you need clarification on my questions please let me know.
EDIT: Here is the adapter code to supplement my question.
private class SpecialAdapter extends ArrayAdapter<JSONArray> {
JSONArray json;
public SpecialAdapter(Context context, int textViewResourceId,
int listItem, JSONArray j) {
super(context, textViewResourceId);
this.json = j;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Log.v(TAG,"I'm inside the getView method.");
try {
JSONObject info = json.getJSONObject(position);
Log.v(TAG,info.toString());
if (convertView == null) {
LayoutInflater i = (LayoutInflater) RecentTaggs.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = i.inflate(R.layout.individual_tagg_view, parent,
false);
holder = new ViewHolder();
holder.username = (TextView) convertView
.findViewById(R.id.userName);
holder.review = (TextView)convertView.findViewById(R.id.review_text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.review.setText(info.getString("review"));
return convertView;
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
My output doesn't output any of my Log.v statements, and doesn't show that its even inside the GetView method.
There is certainly something wrong
with your Adapter. You should never
save Views that should displayed in a
ListView on your own. Only save the
data that is needed to create the
View and overwrite the getView method
of your Adapter to create the needed
view.
The second thing is the list that you
are using in the background task is
visible in the scope of the
doInBackground method only. No change
to this list can have an effect on
your UI.
You are passing the JsonArray to your Adapter before starting the background thread. If your Adapter would be working correctly the ListView should be fully functional before the background thread even starts working. You should pass an empty JsonArray into the ListView and if you are extending an ArrayAdapter you can use addItem in your publish progress method.
Some a little bit unrelated things about the naming of your Adapter class. You called it createSpecialAdapter. This sounds like a method name but it is a classname. Try to start all class names with an uppercase letter and give them names of the things they represent like SpecialAdapter.

Categories

Resources