I am developing an Android app and one of its use cases is to display the following situation.
I have a list of links (L). Each of these links is the URL for a
picture in the Internet;
I have to download each picture of (L) and display it in a ListView. There should be two rows in the ListView(s), where I can insert the pictures. I want to do something similar to this app;
I have to display the pictures in a HorizontalScrollView;
The pictures have to be downloaded on demand, in other words, I just
download the picture using a Thread when the HorizontalScrollView is
in a position that shows this picture (similar to this
situation).
My questions:
Is it possible to insert an ListView in a HorizontalScrollView? (If yes, how do I do it?)
How do I use HorizontalScrollView? I mean, is there any difference on how I use a ListView inside a ScrollView?
Do you know any plugin/project that has the same purposes?
Question 1 - Perhaps you should re-think your design to use a list of HorizonzalListView.
Question 2 - You can created a list of HorizontialListView programmatically, place them inside a LinearLayout wrapped by a vertical scroll view.
Your myhlist.xml layout:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:id="#+id/list_of_hlist_placeholder"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center_horizontal"/>
</ScrollView>
You activity:
public class ListOfHListlActivity extends Activity {
/** List of ArrayAdapter with each bind to a HorizontialListView created programmatically */
private List<MyAdapter> myAdapters = new ArrayList<MyAdapter>();
/** List of your data model */
private List<Object> myDataList;
/**
* Worker thread running in background doing dirty job.
*/
private class DoDirtyJobAsyncTask extends AsyncTask<Void, MyAdapter, Void> {
#Override
protected Void doInBackground(Void... params) {
// do your dirty job here, to populate myDataList
for (Object myData : myDataList) {
MyAdapter myAdapter = new MyAdapter(myData);
myAdapters.add(myAdapter);
publishProgress(myAdapter);
}
return null;
}
#Override
protected void onProgressUpdate(MyAdapter... myAdapters) {
int currViewId = 1;
for (final MyAdapter myAdapter: myAdapters) {
HorizontialListView listview = new HorizontialListView(getApplicationContext(), null);
listview.setId(currViewId);
listview.setAdapter(myAdapter);
listview.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
// I am clickable.
}
});
RelativeLayout listOfHListLayout = (RelativeLayout) findViewById(R.id.list_of_hlist_placeholder);
// don't forget set height here, you know the height issue in HorizontialListView
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT, 40);
listOfHListLayout.addView(listview, layoutParams);
currViewId++;
}
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new DoDirtyJobAsyncTask().execute();
setContentView(R.layout.myhlist);
}
}
You might be interested in this project http://www.dev-smart.com/archives/34
It talks about implementing the Horizontal ListView in Android without the center locking of the Gallery widget
May be its too late but now android has RecylerView which is way more efficient than Listview and also supports horizontal scroll.
Related
I am dynamically adding items to my listview my code works fine but my problem is when the listview is updated it is going to the starting position (items are added but scroll view begins from initial position).I am using listview inside fragment.I want to avoid that scrolling to initial position.
CODE
ListAdapter adapter =
new SimpleAdapter(getContext(), productsList, R.layout.list_notify, new String[]{"id","title","des"},
new int[]{R.id.id, R.id.title,R.id.des});
lv.setAdapter(adapter);
lv.invalidateViews();
Reference : How to refresh Android listview?
ListView Refresh in Android
Refresh Listview in android
Android refresh listview in fragment
How to refresh Android listview?
ListView is officially legacy. Try to use RecyclerView then you will be able to tell that you don't update whole list with methods like notifyItemChanged(position)...
In your case you will call notifyItemRangeInserted(position, count)
https://developer.android.com/guide/topics/ui/layout/recyclerview
Try smoothScrollToPosition on your listview.
See this, pretty similar if I understand correct what you want.
So in order to get that scrolling to stop you basically have to block the listview from laying out its children so first off you have to create a custom listview something like
public class BlockingListView extends ListView {
private boolean mBlockLayoutChildren;
public BlockingListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setBlockLayoutChildren(boolean block) {
mBlockLayoutChildren = block;
}
#Override
protected void layoutChildren() {
if (!mBlockLayoutChildren) {
super.layoutChildren();
}
}
}
then you can use it like this for example
int firstVisPos = mListView.getFirstVisiblePosition();
View firstVisView = mListView.getChildAt(0);
int top = firstVisView != null ? firstVisView.getTop() : 0;
// Block children layout for now
mListView.setBlockLayoutChildren(true);
// Number of items added before the first visible item
int itemsAddedBeforeFirstVisible = ...;
// Change the cursor, or call notifyDataSetChanged() if not using a Cursor
mAdapter.swapCursor(...);
// Let ListView start laying out children again
mListView.setBlockLayoutChildren(false);
// Call setSelectionFromTop to change the ListView position
mListView.setSelectionFromTop(firstVisPos + itemsAddedBeforeFirstVisible, top);
the setBlockLayoutChildren being true is what will stop your listview from scrolling and of course you can set whatever else you would like it to do
you may also just want to look into recyclerview though may make your life easier
I used a gridview instead of my listview and it solved my problem
set 1 item per row can act as listview in my code
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/grid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
**android:numColumns="1"**
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:scrollbarStyle="outsideOverlay"
android:verticalScrollbarPosition="right"
android:scrollbars="vertical">
reference : Custom layout as an item for a grid view
I currently have a FirebaseListAdapter that populates a ListView with the last 5 items:
#Override
public void onViewCreated(View view, Bundle savedInstanceState){
FirebaseListAdapter<HashMap> adapter = new FirebaseListAdapter<HashMap>(getParentFragment().getActivity(),HashMap.class,R.layout.list_item,firebase.limitToLast(5)) {
#Override
protected void populateView(View view, HashMap hashMap, int i) {
String title = hashMap.get("title").toString();
String body = hashMap.get("body").toString();
TextView title_txt = (TextView)view.findViewById(R.id.title);
TextView body_txt = (TextView)view.findViewById(R.id.body);
title_txt.setText(title);
body_txt.setText(body);
}
};
listView.setAdapter(adapter);
}
The problem I have is that when a new item is pushed to Firebase, it is added to the bottom of the list. I want the newest items at the top of the list.
Is it possible to achieve this?
Any help is greatly appreciated, thanks!
This is not the exact solution of your problem of getting the data from firebase in a reverse order, but anyway, we've other work arounds.
The first work around is mentioned in a comment of using a custom adapter for your list.
To achieve the behaviour you need to get the firebase data in a list and then you've to reverse it yourself before passing it to an adapter. Simple!
The second work around is easy as pie and I think if you're using RecyclerView it could do the trick for you and I see it as the simplest way you can do the job.
Here's I'm modifying some of my code with RecyclerView
// Declare the RecyclerView and the LinearLayoutManager first
private RecyclerView listView;
private LinearLayoutManager mLayoutManager;
...
#Override
public void onViewCreated(View view, Bundle savedInstanceState){
// Use FirebaseRecyclerAdapter here
// Here you modify your LinearLayoutManager
mLayoutManager = new LinearLayoutManager(MainActivity.this);
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
// Now set the layout manager and the adapter to the RecyclerView
listView.setLayoutManager(mLayoutManager);
listView.setAdapter(adapter);
}
By setting mLayoutManager.setReverseLayout(true); - you're reversing your layout and mLayoutManager.setStackFromEnd(true); positions the view to the top of your list.
Migrating to RecyclerView is simple. Your layout will be something like this
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<android.support.v7.widget.RecyclerView
android:id="#+id/my_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
And in your build.gradle
dependencies {
compile 'com.android.support:recyclerview-v7:23.4.0'
}
You need to have FirebaseRecyclerAdapter can be found here in FirebaseUI library.
Note: Do not use RecyclerView.LayoutManager as the setReverseLayout and setStackFromEnd functions won't be found in RecyclerView.LayoutManager. Use LinearLayoutManager as stated.
Update
Here's how you can handle the click events of your items in the list.
You had to declare a ViewHolder to implement the RecyclerView right? Just add another function inside your ViewHolder class like the example below and call this function after the setText functions you've got there.
public static class MyViewHolder extends RecyclerView.ViewHolder {
View mView;
public MyViewHolder(View itemView) {
super(itemView);
mView = itemView;
}
public void setClickEvent() {
// Set the onClickListener on mView
// mView.setOnClickListener(new OnClickListener)...
}
}
Since you're already extending FirebaseListAdapter to implement populateView, you can go ahead and override getItem as well to invert the item lookup:
#Override
public HashMap getItem(int pos) {
return super.getItem(getCount() - 1 - pos);
}
If you're not using RecyclerView this is a simple way to reverse the data without extending any more classes than necessary. Obviously this will reverse any AdapterView subclass backed by this adapter, if you choose to reuse it.
I'm trying to use PullToRefreshListView instead my standard ListView however, it did not work for me as a beginner, the listView is using an adapter and inflating xml item file into the listview,
when i replaced my listview wih
</com.handmark.pulltorefresh.library.PullToRefreshListView>
the listview becomes empty, no items are loaded
onItemClickListener()
when i replace the listview from ListView to PullToRefreshListView, i don't get the getItemAtPosition option,
so how can i add an onItemClickListener() if i can't get the position?
list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.handmark.pulltorefresh.library.PullToRefreshListView
android:id="#+id/CarsList"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
my Activity.xml extends Activity
public class MainActivity extends Activity implements AsyncCallBack {
PullToRefreshListView myList;
CustomList cl;
....
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myList = (PullToRefreshListView) findViewById(R.id.CarsList);
...
That's the part of the code... getApiJsonArray is a listener which gets excuted on onPostExecute()
#Override
public JSONArray getApiJsonArray(JSONArray results) {
if (dialog != null) {
dialog.dismiss();
}
ArrayList<CarsItems> carsItems;
try {
carsItems = getCarsList(results);
cl = new CustomList(this, carsItems);
myList.setAdapter(cl);
myList.setOnItemClickListener(new OnItemClickListener() {
problem is in dimensions, the fill_parent is the problem, i check and it worked
By default when you copy and paste the pulltorefresh code into your project you get this :
<com.markupartist.android.widget.PullToRefreshListView
android:id="#+id/android:list"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
/>
i hesitated to ask someone on stackoverflow cuz i frustrated with importing it and updating the support version.....etc
however when you copy the code directly into your project you get a blank list or no list at all
simply change the height and width to something else like match_parent, this should be a straight forward answer for beginners like me.
and if you get a problem not finding the getItemAtPosition() for the setOnItemClickListener() method in the list try to call it inside that method (the setOnItemClickListener())
like that
#Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
Object item = a.getItemAtPosition(position);
..
hope this will help beginners like me! :)
try to trace your code
Log.i("MyApp","Results = "+results.toString());
carsItems = getCarsList(results);
inside CustomList after you set the items inside the adapter add
notifyDataSetChanged(); <-- without this no elements will appear
My app is designed as follows:
Main Activity uses action bars
First tab is a fragment that is split into 3 sections
| Linear Layout containing List view | |Linear Layout containing List View | | Linear Layout containing media controls and image view|
I have two AsyncTasks within this activity, one that fills the centre list view, the other that starts with the media controls to fill the image view (album art).
Both of these are working well. The List view AsyncTask throws a progress dialog spinning wheel. This is coming up in the centre of the screen. I understand that I can put this spinner into the top right of the application. But can i place it either in the top right of the list views linear layout, or centred at the back of the linear layout? That way it would be unobtrusive yet obvious what the progress bar applied to?
Thanks in advance
I understand that I can put this spinner into the top right of the
application. But can i place it either in the top right of the list
views linear layout, or centred at the back of the linear layout?
If you can calculate the position of the view, you would probably be able to position the ProgressDialog where you want(for example see this question I answered Change position of progressbar in code). Also, keep in mind, that this could be very counter intuitive for the user who would see the screen for the dialog and the dialog placed at some weird position(he may not make the correlation between the position of the ProgressDialog and the view for which the data is loaded).
Another option could be to modify the current layout for the ListView part to add on top an initial gone FrameLayout(which will cover the entire ListView) with a background that simulates the background for a screen with a Dialog(this FrameLayout will contain a ProgressBar placed where you want). This FrameLayout would be made visible when the AsyncTask kicks in(it may be wise to block the touch events for the underlining ListView). This way the user can still do stuff in your app and it has a clear indication for the View that is loading its data. Of course this will work well if it's possible for the user to work with the other data independently from the loading ListView.
The answer from Luksprog is so good that I thought I should list the code here: Absolutely perfect
main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/first_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/anchor" >
</ListView>
<View
android:id="#id/anchor"
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_centerVertical="true"
android:background="#99cc00" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/anchor" >
<ListView
android:id="#+id/second_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:id="#+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#33c1c1c1"
android:visibility="gone" >
<ProgressBar
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|top"
android:indeterminate="true" />
</FrameLayout>
</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity {
private String[] mItems = { "Item no.1", "Item no.2", "Item no.3",
"Item no.4", "Item no.5", "Item no.6", "Item no.7", "Item no.8",
"Item no.9", "Item no.10", "Item no.11", };
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
// setup the two ListViews
ListView firstList = (ListView) findViewById(R.id.first_list);
firstList.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, mItems));
firstList.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// just as an example, one you click an item in the first
// ListView
// a custom AsyncTask will kick in to load data in to the second
// ListView
new MyTask(MainActivity.this).execute((Void) null);
}
});
ListView secondList = (ListView) findViewById(R.id.second_list);
secondList.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, mItems));
secondList.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// just to test that you can't click the ListView if the data is
// loading
Toast.makeText(getApplicationContext(), "click",
Toast.LENGTH_SHORT).show();
}
});
}
private class MyTask extends AsyncTask<Void, Void, Void> {
private MainActivity mActivity;
private FrameLayout mFrameOverlay;
public MyTask(MainActivity activity) {
mActivity = activity;
}
#Override
protected void onPreExecute() {
// the AsyncTask it's about to start so show the overlay
mFrameOverlay = (FrameLayout) mActivity.findViewById(R.id.overlay);
// set a touch listener and consume the event so the ListView
// doesn't get clicked
mFrameOverlay.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
mFrameOverlay.setVisibility(View.VISIBLE);
}
#Override
protected Void doInBackground(Void... params) {
// do heavy work
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
//remove the overlay
mFrameOverlay.setVisibility(View.GONE);
// setup the ListView with the new obtained data
String[] obtainedData = { "D1", "D2", "D3" };
ListView theList = (ListView) mActivity
.findViewById(R.id.second_list);
theList.setAdapter(new ArrayAdapter<String>(mActivity,
android.R.layout.simple_list_item_1, obtainedData));
}
}
}
Luksprog, hope you don't mind me posting your code, just didn't want it to vanish from git and this answer be lost to others.
I have a ListView containing a series of notes.
Currently I use a layout animation to slide all the notes in from the side when the list first loads; this works perfectly.
However, I'm trying to figure out how to apply a layout animation only to list items below a certain point. Say I delete an item on the list: I'd like all items below it to shift up into the deleted note's old spot.
I've tried finding a way to customize the animation delays or interpolators by child index but haven't found anything appropriate for this location. Is there a way to do this using a custom layout animation (such as extending LayoutAnimationController) or would I have to do this low level and animate each view individually?
Any suggestions?
Create your animation and call it in your list OnItemClickListener. After that you might use the adapter's notifyDataSetChanged to refresh the list content.
In this example, I created a method called removeListItem with receives the row you want to remove and the position of that row in you list content array.
public class MainActivity extends ListActivity implements OnItemClickListener{
ArrayList<String> values;
ArrayAdapter<String> adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
values = generateMockData(50);
adapter = new ArrayAdapter<String>(
this, android.R.layout.simple_list_item_1, values);
setContentView(R.layout.activity_main);
getListView().setAdapter(adapter);
getListView().setOnItemClickListener(this);
}
private ArrayList<String> generateMockData(int number) {
ArrayList<String> result = new ArrayList<String>();
for(int i = 0; i < number; i++)
result.add(""+i+" "+ (int)Math.random() * 13);
return result;
}
private void removeListItem(View rowView, final int positon) {
Animation anim = AnimationUtils.loadAnimation(this,
android.R.anim.slide_out_right);
anim.setDuration(500);
rowView.startAnimation(anim);
new Handler().postDelayed(new Runnable() {
public void run() {
values.remove(positon);//remove the current content from the array
adapter.notifyDataSetChanged();//refresh you list
}
}, anim.getDuration());
}
public void onItemClick(AdapterView<?> arg0, View row, int position, long arg3) {
if(position == YOUR_INDEX) //apply your conditions here!
removeListItem(row,position);
}
I had a very similar problem and was able to find a solution with a simple View subclass that allows you to use a layout animation controller to animate only the views that you specify. Please see this link:
Can LayoutAnimationController animate only specified Views
In your layout xml, try adding:
<ListView
android:id="#android:id/list"
...
android:animateLayoutChanges="true"/>
This will automatically animate insertions and deletions from your listview.
If you have a custom animation, named anim_translate_left, use instead:
<ListView
android:id="#android:id/list"
...
android:animateLayoutChanges="#anim/anim_translate_left"/>
Source: Google API's