Custom Android Components with XML children - android

I'm an iOS developer that is relatively new with Android and I'm trying to create reusable components that separate the controller logic and View definition. I want a pattern similar to iOS IBOutlets where you define a class that can be used with different xib files or storyboard layouts.
For example, say I want to create a custom progress bar component. I want the user to be able to provide the required children and design and style them separately in xml.
Here's some pseudo code of what I'm trying to accomplish:
layout.xml
<FrameLayout>
<!-- A vertical progress bar -->
<CustomProgressBar>
<LinearLayout orientation="vertical">
<ImageView id="#+id/bar" src="#drawable/bar_image" />
<TextView id="#+id/label" text="Bar 1"/>
</LinearLayout>
</CustomProgressBar>
<!-- A horizontal bar using same controller class -->
<CustomProgressBar>
<LinearLayout orientation="horizontal">
<ImageView src="#drawable/background_image" />
<ImageView id="#+id/bar" src="#drawable/bar_image" />
<TextView id="#+id/label" text="Bar 1"/>
</LinearLayout>
</CustomProgressBar>
<FrameLayout>
Then my custom class might look like:
public class CustomProgressBar extends FrameLayout {
private ImageView bar;
private TextView label;
.
.
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// Store the references of the components
bar = (ImageView) findViewById(R.id.bar);
label = (TextView) findViewById(R.id.label);
// Now I should be able to write general code for this component
// using the reference components I found
}
}
In the above example, the developer is instantiating 2 CustomProgressBars in the same xml file. But each bar's layout is drastically different (child display tree and orientation is different). The obvious problem here is that the xml won't compile because I'm using the same ids for different views in the xml. To fix the compilation issue, I could change the id names, but then the controller class won't know how to find references to those children.
Is there another way to approach this problem?

There are methods in ViewGroup called getChildCount and getChildAt which will allow you to pull the child views by index.
So what you will have to do is something like:
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getProgressViews(this);
}
private ImageView bar;
private TextView label;
private void getProgressViews(ViewGroup viewGroup) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view.getClass().getSuperclass() == ViewGroup.class) {
getProgressViews((ViewGroup) view);
}
if (view instanceof ImageView) {
bar = (ImageView) view;
}
if (view instanceof TextView) {
label = (TextView) view;
}
}
}
If you are looking to use these to also manually draw them to screen you need to override the onLayout method.

Related

Espresso: How do I scroll to an item in a HorizontalScrollView by index?

This is what my HorizontalScrollView looks like:
<HorizontalScrollView
android:layout_below="#id/saved_circuits_title"
android:id="#+id/saved_circuits_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:id="#+id/saved_circuits_scroll"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
In my HomeActivity I have the following relevant code that populates the HorizontalScrollView with bitmaps
onCreate{
...
this.savedCircuitsScroll = (LinearLayout) findViewById(R.id.saved_circuits_scroll);
...
}
updateSavedCircuits(){
...// code to make an ImageView from a retrieved bitmap
newImageImage.setOnClickListener(this.thumbnailListener);
this.savedCircuitsScroll.addView(newImageImage);
...
}
How do I use Espresso to scroll to an ImageView at a specified index in my HorizontalScrollView and click it?
What I've tried
I do not have ID's in my layout xml so an approach such as this does not work:
onView( withId( R.id.button)).perform( scrollTo(), click());
I know you can click on an item by index in a RecyclerView and tried finding an analogous approach for HorizontalScrollViews:
onView(withId(R.id.saved_circuits_scroll))
.perform(HorizontalScrollViewActions.actionOnItemAtPosition(0, click()));
Except HorizontalScrollViewActions does not exist.
or following this blog I tried the following to at least click an item in the HorizontalScrollView at a specified index:
// Click item at position 3
onView(withHorizontalScrollView(R.id.scroll_view).atPosition(3)).perform(click());
// Convenience helper
public static HorizontalScrollViewMatcher withHorizontalScrollView(final int horizontalScrollViewId) {
return new HorizontalScrollViewMatcher(horizontalScrollId);
}
except HorizontalScrollViewMatcher does not exist.
What do we do for HorizontalScrollView? It isn't a descendant of ScrollView so the answer here suggests I need to implement my own custom ViewAction. All I want to do is scroll to an item in a HorizontalScrollView by index and click it. Is this really needed? If this is what I need to do, how do I go about implementing this custom ViewAction?
In my case I got it by using:
onView(allOf(withId(R.id.itemTextView), withEffectiveVisibility(Visibility.VISIBLE), withText(R.string.categories))).perform(scrollTo(), click())
R.id.itemTextView is a TextView (with text R.string.categories) added dynamically to the LinearLayout:
<HorizontalScrollView>
<LinearLayout>
... [child added dynamically]
</LinearLayout>
</HorizontalScrollView>
Try to add this matcher.
public static Matcher<View> withIdAndParentId(final int viewId, final int parentId) {
Assert.assertTrue(viewId != -1);
Assert.assertTrue(parentId != -1);
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
}
#Override
public boolean matchesSafely(View view) {
return view.getId() == viewId && isThereParentWithIdInHierarchy(view);
}
private boolean isThereParentWithIdInHierarchy(View view) {
ViewParent viewParent = view.getParent();
if (viewParent == null || !(viewParent instanceof ViewGroup))
return false;
ViewGroup parent = (ViewGroup) viewParent;
return parent.getId() == parentId || isThereParentWithIdInHierarchy(parent);
}
};
}
This is the way to use it :
onView(withIdAndParentId(R.id.YOUR_PARTICULAR_VIEW_ID, R.id.horizontalScrollViewId)).perform(scrollTo(), click());
Hope, it helps.
OK so in my particular case I found out that my scroll views had no ID associated with them (or none that I could reach reasonably, hence I couldn't use Mody's answer). However, they did have a tag associated with them so I could use Espresso's withTagValue ViewMatcher instead. Each view was associated with a circuitProject object (of course for you it may be different). And I have access to the following:
ArrayList<CircuitProject> circuitProjects = new ArrayList<>();
The index of a circuitProject object happens to be the position of the associated view in the HorizontalScrollView. The tag is the folder the circuitProject object is saved to. From here it is straightforward to get the behaviour I need with Espresso scrolling to a particular index in the HorizontalScrollView:
onView(withTagValue(withStringMatching(circuitProject.getFolderID()))).perform(scrollTo(), click());

How to make a set of views invisible in android

I'm trying to make a set of views (that include several textviews and buttons - all in different parent layouts, but in the same activity) invisible if a particular condition is evaluated to false.
The conventional way to do that would be:
findViewById(R.id.myview).setVisibility(View.INVISIBLE);
My question is, will I have to do this for all the views one by one that I want to be invisible, And then toggle them back when I want them visible?
Or, is there a way to avoid the code-repetition?
If the Views are in different parents , you can't do it directly, but you can implement a method to change the visibility of a bunch of Views if you want to keep your code clean:
List<View> relatedViews = new ArrayList<>();
// ...
relatedViews.add(view1);
relatedViews.add(view2);
relatedViews.add(view3);
// ...
changeVisibility(relatedViews, View.INVISIBLE);
// ...
private void changeVisibility(List<View> views, int visibility) {
for (View view : views) {
view.setVisibility(visibility);
}
}
As a side note, you may want to change the visibility to View.GONE instead of View.INVISIBLE so it doesn't take any space in the layout.
you can use something like this. It's not the most elegant solution but works.
The idea is give to each view that you want to hide a same content description, because in the same layout you can not use same id for multiple view. With the same content description you can find all views in your layout and hide them.
That's an example considering the first layout as Linear. You can change obviously ;)
public class TestActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
LinearLayout rootLayout = (LinearLayout)findViewById(R.id.rootLayout);
int childcount = rootLayout.getChildCount();
for (int i=0; i < childcount; i++){
View v = rootLayout.getChildAt(i);
if(v.getContentDescription() != null && v.getContentDescription().equals("invisibleView")){
v.setVisibility(View.INVISIBLE);
//I suggest you to use GONE instead of INVISIBLE to remove the space of the view
}
}
}
}
in your xml give to the object that you want to hide this property
android:contentDescription="invisibleView"
Use varargs method for show or hide multiple views.
for example if you have views like view1, view2.....etc
then just call setVisibility(View.VISIBLE,view1,view2)
public static void setVisibility(int visibility, View... views){
for (View view : views){
view.setVisibility(visibility);
}
}

Android Layout make all children's not clickable

I am using Relative Layout and many buttons in it with TextViews etc.I want to make all of them not clickable unless some event happens.
I tried setting RelativeLayout.setClickable(false); but still all the elements inside the layout are clickable.
I know one way of doing it that setting each child element not clickable but it is not an appropriate way because i have lot of child elements like buttons text views etc inside a layout i cannot make each child not clickable.
Here my question is How to set all to setClickable(false); in layout ??
I found an alternative way to achieve this. You may create a blocking LinearLayout on top all its sibling views in the RelativeLayout like below:
<RelativeLayout
android:id="#+id/rl_parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- the children views begins -->
...
<!-- the children views ends -->
<LinearLayout
android:id="#+id/ll_mask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent"
android:clickable="true"
android:orientation="horizontal"
android:visibility="visible"/>
</RelativeLayout>
Later you may toggle the LinearLayout's visibility to GONE to allow the children views to be clickable again:
mMask.setVisibility(View.VISIBLE); // blocks children
mMask.setVisibility(View.GONE); // children clickable
When you say click do you actually mean touch? Touch events are done element by element. They're first sent to the top level, which says if it handled it and if it didn't it goes onto each children of the view.
When a touch event is created, the onTouch method of view in the chain is called, if any of these return true (true meaning "I handled this!") it stops going down to the children.
To make your relativelayout block touches and clicks for all of its children you simply need to set the onTouchListener, like this:
YOUR_RELATIVE_LAYOUT.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// ignore all touch events
return true;
}
});
This will ignore all touch events happening on the relative layout (and all of its children) which includes simple touch down then release events (called clicks).
You can use following function to find all the child view and cancel click.
public void setClickable(View view) {
if (view != null) {
view.setClickable(false);
if (view instanceof ViewGroup) {
ViewGroup vg = ((ViewGroup) view);
for (int i = 0; i < vg.getChildCount(); i++) {
setClickable(vg.getChildAt(i));
}
}
}
}
A very simple and full-proof way to do it is to create a sub class and override onInterceptTouchEvent:
public class MyRelativeLayout extends RelativeLayout {
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// true if you do not want the children to be clickable.
return mShouldInterceptAllTouch;
}
}
No need to call any of the children's methods.
You can still call setOnClickListener on your myRelativeLayout object. Also, you can use the class in XMLs as if you were using a RelativeLayout
An easy Kotlin extension solution to disable/enable a view and all of it's children:
fun View.isUserInteractionEnabled(enabled: Boolean) {
isEnabled = enabled
if (this is ViewGroup && this.childCount > 0) {
this.children.forEach {
it.isUserInteractionEnabled(enabled)
}
}
}
and call it with:
view.isUserInteractionEnabled(false)
If you want the children of a ViewGroup to be unresponsive to touch events, but you want the ViewGroup itself to respond to clicks, for example, you can create your own ViewGroup subclass, override onInterceptTouchEvent(), and always return true. This will intercept all touch events before children see them, while allowing your custom ViewGroup to remain responsive to touch events.
So, instead of RelativeLayout, you could use your own subclass:
public class ControlFreakRelativeLayout extends RelativeLayout {
private boolean mWithholdTouchEventsFromChildren;
// Constructors omitted for sake of brevity
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mWithholdTouchEventsFromChildren || super.onInterceptTouchEvent(ev);
}
public void setWithholdTouchEventsFromChildren(boolean withholdTouchEventsFromChildren) {
mWithholdTouchEventsFromChildren = withholdTouchEventsFromChildren;
}
}
Problem : same as original question, but different use case. i want to progressBar widget which in container with transparent background colors. i want to disable all clicks on other items which comes under my transparent background color .
solution : just set your container clickable in my case Relative layout, it can any other layout too, keep your layout clickable true, until you want according to condition in my case till api call completed. after it simply set your parent clickable false.
android xml
<RelativeLayout
android:id="#+id/rl_parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- the children views begins -->
...
<!-- the children views ends -->
</RelativeLayout>
Java:
rl_parent.setClickable(true); // when to disable all clicks of children view
rl_parent.setClickable(false); // when to enable all clicks of children view
Since you are going to perform the click for some items in the layout when that particular function is executed,
You can keep a static flag like a boolean
Create a static boolean globally and declare it as false, once the particular function is performed change it to true.
so in all the onclick functions that u r performing check this flag if it is true perform the necessory function.
You can make a common method in which you can write the code for disabling all your views inside your relative layout and call this method in onCreate() and then you can enable your particular view inside the the layout on your event.
If Butterknife library is used, the views can be grouped and the functionality can be done on the group.
Refer http://jakewharton.github.io/butterknife/,
#BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
The apply method allows you to act on all the views in a list at once.
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
Action and Setter interfaces allow specifying simple behavior.
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
#Override public void apply(View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
#Override public void set(View view, Boolean value, int index) {
view.setEnabled(value);
}
};
For eg., if the textviews are grouped, you could do
static final ButterKnife.Setter<TextView, Boolean> ENABLED = new ButterKnife.Setter<TextView, Boolean>() {
#Override public void set(TextView view, Boolean value, int index) {
view.setClickable(value);
view.setLongClickable(value);
if(value){
view.setTextColor(color);
} else {
view.setTextColor(color);
}
}
};
As an alternative way, you can make clickable ViewGroup with children views via FrameLayout instead of RelativeLayout. Just position your child views in FrameLayout using paddings and gravity, make FrameLayout clickable, and all children views non-clickable:
<FrameLayout
android:layout_width="51dp"
android:layout_height="59dp"
android:layout_gravity="center"
android:clickable="true"
android:focusable="true"
android:onClick="#{() -> activity.onClick()}"
android:padding="5dp">
<android.support.v7.widget.AppCompatImageButton
android:layout_width="wrap_content"
android:layout_height="29dp"
android:layout_gravity="center"
android:clickable="false"
app:srcCompat="#drawable/ic_back" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:clickable="false"
android:text="#string/back" />
</FrameLayout>
I would recommend creating an extension function something like extensions.kt
import android.view.View
fun View.disable() {
isEnabled = false
isClickable = false
alpha = 0.5F
}
fun View.enable() {
isEnabled = true
isClickable = true
alpha = 1F
}
obviously, you can customize the value as you need
and now you can use it on any View like
somethingView.enable()
imageView.disable()
myButton.enable()
textView.diable()
loop on your buttons and use setClickable function and pass false value for it.
then when your even happen loop again on ot and setClickable to true

Custom Android control with children

I'm trying to create a custom Android control that contains a LinearLayout. You can think of it as an extended LinearLayout with fancy borders, a background, an image on the left...
I could do it all in XML (works great) but since I have dozens of occurences in my app it's getting hard to maintain. I thought it would be nicer to have something like this:
/* Main.xml */
<MyFancyLayout>
<TextView /> /* what goes inside my control's linear layout */
</MyfancyLayout>
How would you approach this? I'd like to avoid re-writing the whole linear layout onMeasure / onLayout methods. This is what I have for the moment:
/* MyFancyLayout.xml */
<TableLayout>
<ImageView />
<LinearLayout id="container" /> /* where I want the real content to go */
</TableLayout>
and
/* MyFancyLayout.java */
public class MyFancyLayout extends LinearLayout
{
public MyFancyLayout(Context context) {
super(context);
View.inflate(context, R.layout.my_fancy_layout, this);
}
}
How would you go about inserting the user-specified content (the TextView in main.xml) in the right place (id=container)?
Cheers!
Romain
----- edit -------
Still no luck on this, so I changed my design to use a simpler layout and decided to live with a bit of repeated XML. Still very interested in anyone knows how to do this though!
This exact question bugged me for some time already but it's only now that I've solved it.
From a first glance, the problem lies in the fact that a declarative content (TextView in Your case) is instantiated sometime after ctor (where we're usually inflating our layouts), so it's too early have both declarative and template content at hand to push the former inside the latter.
I've found one such place where we can manipulate the both: it's a onFinishInflate() method. Here's how it goes in my case:
#Override
protected void onFinishInflate() {
int index = getChildCount();
// Collect children declared in XML.
View[] children = new View[index];
while(--index >= 0) {
children[index] = getChildAt(index);
}
// Pressumably, wipe out existing content (still holding reference to it).
this.detachAllViewsFromParent();
// Inflate new "template".
final View template = LayoutInflater.from(getContext())
.inflate(R.layout.labeled_layout, this, true);
// Obtain reference to a new container within "template".
final ViewGroup vg = (ViewGroup)template.findViewById(R.id.layout);
index = children.length;
// Push declared children into new container.
while(--index >= 0) {
vg.addView(children[index]);
}
// They suggest to call it no matter what.
super.onFinishInflate();
}
A labeled_layout.xml referenced above is not unlike something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation ="vertical"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:layout_marginBottom ="3dip"
android:layout_weight ="1"
android:duplicateParentState ="true">
<TextView android:id ="#+id/label"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:singleLine ="true"
android:textAppearance ="?android:attr/textAppearanceMedium"
android:fadingEdge ="horizontal"
android:duplicateParentState="true" />
<LinearLayout
android:id ="#+id/layout"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:duplicateParentState="true" />
</LinearLayout>
Now (still omitting some details) elsewhere we might use it like this:
<com.example.widget.LabeledLayout
android:layout_width ="fill_parent"
android:layout_height ="wrap_content">
<!-- example content -->
</com.example.widget.LabeledLayout>
This approach saves me a lot of code! :)
As esteewhy explains, just swap the xml-defined contents into where you want them internally in your own layout, in onFinishInflate(). Example:
I take the contents that I specify in the xml:
<se.jog.custom.ui.Badge ... >
<ImageView ... />
<TextView ... />
</se.jog.custom.ui.Badge>
... and move them to my internal LinearLayout called contents where I want them to be:
public class Badge extends LinearLayout {
//...
private LinearLayout badge;
private LinearLayout contents;
// This way children can be added from xml.
#Override
protected void onFinishInflate() {
View[] children = detachChildren(); // gets and removes children from parent
//...
badge = (LinearLayout) layoutInflater.inflate(R.layout.badge, this);
contents = (LinearLayout) badge.findViewById(R.id.badge_contents);
for (int i = 0; i < children.length; i++)
addView(children[i]); //overridden, se below.
//...
super.onFinishInflate();
}
// This way children can be added from other code as well.
#Override
public void addView(View child) {
contents.addView(child);
}
Combined with custom XML attributes things gets very maintainable.
You can create your MyFancyLayout class by extending LinearLayout. Add the three constructors which call a method ("initialize" in this case) to set up the rest of the Views:
public MyFancyLayout(Context context) {
super(context);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
Within initialize, you do anything you need to to add the extra views. You can get the LayoutInflater and inflate another layout:
final LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.somecommonlayout, this);
Or you can create Views in code and add them:
ImageView someImageView = new ImageView(getContext());
someImageView.setImageDrawable(myDrawable);
someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(someImageView);
If you're going to use the Context a lot, you can store a reference to it in your constructors and use that rather than getContext() to save a little overhead.
just use something like this:
<org.myprogram.MyFancyLayout>
...
</org.myprogram.MyFancyLayout>
Useful link - http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html

Horizontal ListView in Android?

Is it possible to make the ListView horizontally? I have done this using a gallery view, but the selected item comes to the center of the screen automatically. I don't want the selected item at the same spot I clicked. How can I rectify this problem? My idea was to set the ListView with a horizontal scroll. Share your idea?
As per Android Documentation RecyclerView is the new way to organize the items in listview and to be displayed horizontally
Advantages:
Since by using Recyclerview Adapter, ViewHolder pattern is
automatically implemented
Animation is easy to perform
Many more features
More Information about RecyclerView:
grokkingandroid.com
antonioleiva.com
Sample:
survivingwithandroid.com
Just add the below block to make the ListView to horizontal from vertical
Code-snippet
LinearLayoutManager layoutManager= new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL, false);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(layoutManager);
Paul doesn't bother to fix bugs of his library or accept users fixes. That's why I am suggesting another library which has similar functionality:
https://github.com/sephiroth74/HorizontalVariableListView
Update: on Jul 24, 2013 author (sephiroth74) released completely rewritten version based on code of android 4.2.2 ListView. I must say that it doesn't have all the errors which previous version had and works great!
#Paul answer links to a great solution, but the code doesn't allow to use onClickListeners on items children (the callback functions are never called). I've been struggling for a while to find a solution and I've decided to post here what you need to modify in that code (in case somebody need it).
Instead of overriding dispatchTouchEvent override onTouchEvent. Use the same code of dispatchTouchEvent and delete the method (you can read the difference between the two here http://developer.android.com/guide/topics/ui/ui-events.html#EventHandlers )
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean handled = mGesture.onTouchEvent(event);
return handled;
}
Then, add the following code which will decide to steal the event from the item children and give it to our onTouchEvent, or let it be handled by them.
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch( ev.getActionMasked() ){
case MotionEvent.ACTION_DOWN:
mInitialX = ev.getX();
mInitialY = ev.getY();
return false;
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(ev.getX() - mInitialX);
float deltaY = Math.abs(ev.getY() - mInitialY);
return ( deltaX > 5 || deltaY > 5 );
default:
return super.onInterceptTouchEvent(ev);
}
}
Finally, don't forget to declare the variables in your class:
private float mInitialX;
private float mInitialY;
Since Google introduced Android Support Library v7 21.0.0, you can use RecyclerView to scroll items horizontally. The RecyclerView widget is a more advanced and flexible version of ListView.
To use RecyclerView, just add dependency:
com.android.support:recyclerview-v7:23.0.1
Here is a sample:
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
MyAdapter adapter = new MyAdapter(myDataset);
recyclerView.setAdapter(adapter);
}
}
More info about RecyclerView:
https://developer.android.com/training/material/lists-cards.html
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
This is a little (very) late, but I'm posting this in case someone comes by this later.
The Support Library as of the Android L preview has a RecyclerView that does exactly what you want.
Right now, you can only get it through the L preview SDK and you need to set your minSdk to L. But you can copy all of the necessary files into your project and use them that way until L is officially out.
You can download the preview docs here.
Warning: The API for Recycler View may change and it may have bugs.
Updated
The source code for horizontal listview is:
LinearLayoutManager layoutManager
= new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
RecyclerView myList = findViewById(R.id.my_recycler_view);
myList.setLayoutManager(layoutManager);
Download the jar file from here
now put it into your libs folder, right click it and select 'Add as library'
now in main.xml put this code
<com.devsmart.android.ui.HorizontalListView
android:id="#+id/hlistview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
now in Activity class if you want Horizontal Listview with images then put this code
HorizontalListView hListView = (HorizontalListView) findViewById(R.id.hlistview);
hListView.setAdapter(new HAdapter(this));
private class HAdapter extends BaseAdapter {
LayoutInflater inflater;
public HAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return Const.template.length;
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
HViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.listinflate, null);
holder = new HViewHolder();
convertView.setTag(holder);
} else {
holder = (HViewHolder) convertView.getTag();
}
holder.img = (ImageView) convertView.findViewById(R.id.image);
holder.img.setImageResource(Const.template[position]);
return convertView;
}
}
class HViewHolder {
ImageView img;
}
Its actually very simple:
simply Rotate the list view to lay on its side
mlistView.setRotation(-90);
Then upon inflating the children, that should be inside the getView method. you rotate the children to stand up straight:
mylistViewchild.setRotation(90);
Edit:
if your ListView doesnt fit properly after rotation, place the ListView inside this RotateLayout like this:
<com.github.rongi.rotate_layout.layout.RotateLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:angle="90"> <!-- Specify rotate angle here -->
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</com.github.rongi.rotate_layout.layout.RotateLayout>
My solution is to simply use ViewPager widget. It isn't center-locked as Gallery and has a built-in features for recycling views (as ListView). You may see similar approach at Google Play app, whenever you deal with horizontally scrollable lists.
You just need to extend PagerAdapter and perform a couple of tweaks there:
public class MyPagerAdapter extends PagerAdapter {
private Context mContext;
public MyPagerAdapter(Context context) {
this.mContext = context;
}
// As per docs, you may use views as key objects directly
// if they aren't too complex
#Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.item, null);
container.addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
#Override
public int getCount() {
return 10;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
// Important: page takes all available width by default,
// so let's override this method to fit 5 pages within single screen
#Override
public float getPageWidth(int position) {
return 0.2f;
}
}
As result, you'll have horizontally scrollable widget with adapter, like this:
Note: Android now supports horizontal list views using RecyclerView, so now this answer is deprecated, for information about RecyclerView :
https://developer.android.com/reference/android/support/v7/widget/RecyclerView
I have developed a logic to do it without using any external horizontal scrollview library, here is the horizontal view that I achieved and I have posted my answer here:https://stackoverflow.com/a/33301582/5479863
My json response is this:
{"searchInfo":{"status":"1","message":"Success","clist":[{"id":"1de57434-795e-49ac-0ca3-5614dacecbd4","name":"Theater","image_url":"http://52.25.198.71/miisecretory/category_images/movie.png"},{"id":"62fe1c92-2192-2ebb-7e92-5614dacad69b","name":"CNG","image_url":"http://52.25.198.71/miisecretory/category_images/cng.png"},{"id":"8060094c-df4f-5290-7983-5614dad31677","name":"Wine-shop","image_url":"http://52.25.198.71/miisecretory/category_images/beer.png"},{"id":"888a90c4-a6b0-c2e2-6b3c-561788e973f6","name":"Chemist","image_url":"http://52.25.198.71/miisecretory/category_images/chemist.png"},{"id":"a39b4ec1-943f-b800-a671-561789a57871","name":"Food","image_url":"http://52.25.198.71/miisecretory/category_images/food.png"},{"id":"c644cc53-2fce-8cbe-0715-5614da9c765f","name":"College","image_url":"http://52.25.198.71/miisecretory/category_images/college.png"},{"id":"c71e8757-072b-1bf4-5b25-5614d980ef15","name":"Hospital","image_url":"http://52.25.198.71/miisecretory/category_images/hospital.png"},{"id":"db835491-d1d2-5467-a1a1-5614d9963c94","name":"Petrol-Pumps","image_url":"http://52.25.198.71/miisecretory/category_images/petrol.png"},{"id":"f13100ca-4052-c0f4-863a-5614d9631afb","name":"ATM","image_url":"http://52.25.198.71/miisecretory/category_images/atm.png"}]}}
Layout file :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="5">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4" />
<HorizontalScrollView
android:id="#+id/horizontalScroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:id="#+id/ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
class file:
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.ll);
for (int v = 0; v < collectionInfo.size(); v++) {
/*---------------Creating frame layout----------------------*/
FrameLayout frameLayout = new FrameLayout(ActivityMap.this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, getPixelsToDP(90));
layoutParams.rightMargin = getPixelsToDP(10);
frameLayout.setLayoutParams(layoutParams);
/*--------------end of frame layout----------------------------*/
/*---------------Creating image view----------------------*/
final ImageView imgView = new ImageView(ActivityMap.this); //create imageview dynamically
LinearLayout.LayoutParams lpImage = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
imgView.setImageBitmap(collectionInfo.get(v).getCatImage());
imgView.setLayoutParams(lpImage);
// setting ID to retrieve at later time (same as its position)
imgView.setId(v);
imgView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// getting id which is same as its position
Log.i(TAG, "Clicked on " + collectionInfo.get(v.getId()).getCatName());
// getting selected category's data list
new GetSelectedCategoryData().execute(collectionInfo.get(v.getId()).getCatID());
}
});
/*--------------end of image view----------------------------*/
/*---------------Creating Text view----------------------*/
TextView textView = new TextView(ActivityMap.this);//create textview dynamically
textView.setText(collectionInfo.get(v).getCatName());
FrameLayout.LayoutParams lpText = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER);
// Note: LinearLayout.LayoutParams 's gravity was not working so I putted Framelayout as 3 paramater is gravity itself
textView.setTextColor(Color.parseColor("#43A047"));
textView.setLayoutParams(lpText);
/*--------------end of Text view----------------------------*/
//Adding views at appropriate places
frameLayout.addView(imgView);
frameLayout.addView(textView);
linearLayout.addView(frameLayout);
}
private int getPixelsToDP(int dp) {
float scale = getResources().getDisplayMetrics().density;
int pixels = (int) (dp * scale + 0.5f);
return pixels;
}
trick that is working here is the id that I have assigned to ImageView "imgView.setId(v)" and after that applying onClickListener to that I am again fetching the id of the view....I have also commented inside the code so that its easy to understand,
I hope this may be very useful...
Happy Coding... :)
This isn't much of an answer, but how about using a Horizontal Scroll View?
You can use RecyclerView in the support library. RecyclerView is a generalized version of ListView that supports:
A layout manager for positioning items
Default animations for common
item operations
Android Recycler View Docs
I've done a lot of searching for a solution to this problem. The short answer is, there is no good solution, without overriding private methods and that sort of thing. The best thing I found was to implement it myself from scratch by extending AdapterView. It's pretty miserable. See my SO question about horizontal ListViews.
I had to do the same for one of my projects and I ended up writing my own as well. I called it HorzListView is now part of my open source Aniqroid library.
http://aniqroid.sileria.com/doc/api/ (Look for downloads at the bottom or use google code project to see more download options: http://code.google.com/p/aniqroid/downloads/list)
The class documentation is here: http://aniqroid.sileria.com/doc/api/com/sileria/android/view/HorzListView.html
For my application, I use a HorizontalScrollView containing LinearLayout inside, which has orientation set to horizontal. In order to add images inside, I create ImageViews inside the activity and add them to my LinearLayout. For example:
<HorizontalScrollView
android:id="#+id/photo_scroll"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="horizontal"
android:visibility="gone">
<LinearLayout
android:id="#+id/imageview_holder"
android:layout_width="wrap_content"
android:orientation="horizontal"
android:layout_height="match_parent">
</LinearLayout>
</HorizontalScrollView>
An this works perfectly fine for me. In the activity all I have to do is something like the code below:
LinearLayout imgViewHolder = findViewById(R.id.imageview_holder);
ImageView img1 = new ImageView(getApplicationContext());
//set bitmap
//set img1 layout params
imgViewHolder.add(img1);
ImageView img2 = new ImageView(getApplicationContext());
//set bitmap
//set img2 layout params
imgViewHolder.add(img2);
As I said that works for me, and I hope it helps somebody looking to achieve this as well.
well you can always create your textviews etc dynamically and set your onclicklisteners like you would do with an adapter
HorizontialListView can't work when the data in the adapter is involved in another thread. Everything runs 100% on UI thread.This is a big problem in multithread. I think using HorizontialListView is not the best solution for your problem.HorzListView is a better way.You just replace your previous Gallery with HorzListView.You neednot modify the code about the adapter.Then everything goes the way you hope.See https://stackoverflow.com/a/12339708/1525777 about HorzListView.
I had used horizontal listview link in my project & I got good results. I had been used devsmart library initially but it gave me some issues. So best way to use horizontal listview link as it recovered my issues & also I recently launched my app on Google PlayStore using this library & got nice response from users. So I recommend you to use the same library which I mentioned above to show listview horizontally. Enjoy :)
There is a great library for that, called TwoWayView, it's very easy to implement, just include the project library into your work space and add it as a library project to your original project, and then follow the following steps which are originally mentioned here:
First, let's add a style indicating the orientation of the ListView
(horizontal or vertical) in (res/values/styles.xml):
<style name="TwoWayView">
<item name="android:orientation">horizontal</item>
</style>
Then,
In your Layout XML, use the following code to add the TwoWayView:
<org.lucasr.twowayview.TwoWayView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/lvItems"
style="#style/TwoWayView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false"
tools:context=".MainActivity" />
and finally, just declare it and deal with it like any regular ListView:
TwoWayView lvTest = (TwoWayView) findViewById(R.id.lvItems);
All the methods of ListView will work here as usual, but there is only one difference I noticed, which is when setting the choice mode, the method setChoiceMode not takes an int value but a value from enum called ChoiceMode, so list_view.setChoiceMode(ListView.CHOICE_MODE_SINGLE); will be lvTest.setChoiceMode(ChoiceMode.SINGLE); // or MULTIPLE or NONE.
You may use ViewFlipper to include the layout XML and add images , listview for each layout XML

Categories

Resources