I'm trying to make two side by side ListViews act like a GridView to an extent. The reason I'm not using GridView is because there's no support for a Staggered Look. Anyways, I have the following code so far:
<- Old,now irrelevant code ->
EDIT:
I did as #Sam suggested and used the following code:
lv1.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (touchSource == null) {
touchSource = v;
}
if (v == touchSource) {
lv2.dispatchTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
clickSource = v;
touchSource = null;
}
}
return false;
}
});
lv1.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (parent == clickSource) {
//my own code here
}
}
});
lv1.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (view == clickSource) {
}
boolean loadMore = /* maybe add a padding */
firstVisibleItem + visibleItemCount + 10 >= totalItemCount;
if (loadMore) {
//add items, load more
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
lv2.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (touchSource == null) {
touchSource = v;
}
if (v == touchSource) {
lv1.dispatchTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
clickSource = v;
touchSource = null;
}
}
return false;
}
});
lv2.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (parent == clickSource) {
}
}
});
lv2.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (view == clickSource) {
}
boolean loadMore = /* maybe add a padding */
firstVisibleItem + visibleItemCount + 2 >= totalItemCount;
if (loadMore) {
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
I am also using a header on one of the lists to create a Staggered Effect and I need to keep this stagger at all costs. This is mostly working (the above code) but it unsyncs a lot of the time. I have figured out this is only when I have small short swipes. I'm not sure why, and I can't find a good solution. It seems to me that the above logic should work.
Also, just in case it matters, I am using UniversalImageLoader to load photos and a simple EndlessScroll logic to load more photos (directly calling adapter.notifyDataSetChanged()). This stuff doesn't seem too relevant though. Even when photos are loaded, I can still see it unsyncing.
I want to be clear: if I do short swipes (and sometimes just in general repeated scrolling) I can unsync the lists at will.
Any help is appreciated. Thanks.
EDIT 2:
Yet to find a good solution. Here are the solutions that I have found that don't quite work:
The above solution: unsyncs far too often
Pinterest List View: no OnScrollListener/onItemClick differentiation
StaggeredGridView: No onScrollListener
Stag Project: No onItemClick listener
Linear Layout Method: No onItemClick Listener
Any ideas? Thanks.
Okay, so I ended up using the Linear Layout Method(See the Question) and used setTag to get the onItemClickListener working, and a CustomScrollView implementation to get the infinite List working.
Basically, two linearLayouts stacked horizontally, inside a scrollview. It works rather well, and loads much faster.
Also, to load my images SmartImageView if that helped.
If someone wants the code, I might be able to post parts of it.
Instead of listening to onTouch why don't you scroll lists in the onScroll listener of ListViews. That's what I'm using in that library with sort of tracks the scroll of a ListView similarly and it works like a charm. Check out this file. https://github.com/JlUgia/list_moving_container/blob/master/src/com/ugia/listmovingcontainer/fragment/BottomListMovingContainerFragment.java
Pay attention specially to 81 (don't forget to add the global layout updater listener) and 89 (with the onScroll Listener).
That way you can forget about the click hacks as well.
UPDATE
I'd code it the following way.
lv1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
updateListPosition(lv2, lv1);
}
});
lv1.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
updateListPosition(lv2, lv1);
}
});
lv2.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
updateListPosition(lv1, lv2);
}
});
lv2.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
updateListPosition(lv1, lv2);
}
});
private void updateListPosition(ListView updatedList, ListView scrolledList){
updatedList.setScrollY(scrolledList.getScrollY());
}
** setScrollY already invalidates your list.
** Note that I didn't test the code. Although it should be simple enough.
Related
Hello i`m trying to get recyclerview viewholder at particular position with this code:
val viewHolder = educationRv.findViewHolderForAdapterPosition(i) as SectionsRecyclerAdapter.ViewHolder
the problem is that recycler view recycle not visible items and i can't get item and position 0 if its not visible. I searched before ask i tried this solutions, but can't prevent recycling of the views (i know that this is not good solution but i`m stuck and can't figure it out better) :
educationRv.recycledViewPool.setMaxRecycledViews(1,0)
override fun getItemViewType(position: Int): Int {
return 1
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder != null) {
holder.setIsRecyclable(false)
}
Generalized -> i have recyclerview with 20 edittexts which can be added dynamically, when user click save i need to get all edittexts values and save it to array, the problem comes when button save is clicked only two or three views are visible when i try to get not visible return null
As i got your query this solution would definitely help you
In you viewHolder do like this
etText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
yourEditTextList.set(getAdapterPosition(), s.toString());
}
#Override
public void afterTextChanged(Editable s) {
}
});
btnAddNewEditText.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
yourEditTextList.add(new String(""));
notifyDataSetChanged();
}
});
Here yourEditTextList is the list of String in of your editText in your recylerView
In your activity where you have recylerView
btnDone.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Fetch you list that your have passed on recycler view you will get here
}
});
After a thorough search and quite a lot of thinking, I couldn't find a solution to the following problem in AndroidStudio:
I have 2 spinners (input and output). I want to pass the value of the input spinner to a method that is called upon selection of a value of the output spinner (onItemSelected). The regarding code passage looks as follows:
private void setupSpinnerListeners() {
spinnerLengthInput.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String itemSelectedInSpinnerLengthInput = parent.getItemAtPosition(position).toString();
checkIfConvertingFromMeter(itemSelectedInSpinnerLengthInput);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spinnerLengthOutput.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String itemSelectedInSpinnerLengthOutput = parent.getItemAtPosition(position).toString();
updateOutputTextfield(itemSelectedInSpinnerLengthInput, itemSelectedInSpinnerLengthOutput);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
I want the String itemSelectedInSpinnerLengthInput (that gets its value from the input spinner) to be available in the onItemSelected method of the output spinner. How to accomplish this?
Any help is greatly appreciated.
EDIT: Create a global variable INSIDE the setupSpinnerListeners Method, that is an array with length 1. The it'll work as I had intended.
I recommend you to use OnItemSelectedListener.
Then create a globalVariable to get the String to your first Spinner as follows :
String FirstValue = "";
Then you'll need to call this :
spinnerLengthInput.setOnItemSelectedListener(this);
spinnerLengthOutput.setOnItemSelectedListener(this);
Of course you'll need to implements OnItemSelectedListener
Then inside you can do the same that you were doing.
#Override
public void onItemSelected(AdapterView<?> spinner, View view, int position,long id)
{
FirstValue = spinner.getItemAtPosition(position).toString();
checkIfConvertingFromMeter(itemSelectedInSpinnerLengthInput);
}
Then in your other Spinner use the FirstValue value.
You should change itemSelectedInSpinnerLengthOutput as a global variable. After that, you can easy access to it in the onItemSelected method of the output spinner
String itemSelectedInSpinnerLengthInput; // global variable
private void setupSpinnerListeners() {
spinnerLengthInput.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
itemSelectedInSpinnerLengthInput = parent.getItemAtPosition(position).toString();
checkIfConvertingFromMeter(itemSelectedInSpinnerLengthInput);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spinnerLengthOutput.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String itemSelectedInSpinnerLengthOutput = parent.getItemAtPosition(position).toString();
if(itemSelectedInSpinnerLengthInput != null){
updateOutputTextfield(itemSelectedInSpinnerLengthInput, itemSelectedInSpinnerLengthOutput);
}else{
Toast.makeText(getApplicationContext(), "please select input", Toast.LENGTH_LONG).show();
...
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
Hope this help
How can I detect when the listview item has been clicked a second time?
I have made it so that when an item in the listview is clicked, the color is set to green. Now what I want is the color to change back on a second click.
Can anyone explain how I can do so?
Heres where I set the color green:
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id){
parent.getChildAt(position).setBackgroundColor(Color.GREEN);
}
});
If you are trying to modify anything from your cell it not gonna work for the second click.
For that, try to make your listener inside your adapter and then change the color of the background (convertView.setBackground(*)). For me it works like below:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (viewHolder.addedButton.getVisibility() == View.INVISIBLE) {
viewHolder.addedButton.setVisibility(View.VISIBLE);
convertView.setBackground(any_color_you_want);
listener.onButtonAdd(v, position);
} else {
viewHolder.addedButton.setVisibility(View.INVISIBLE);
convertView.setBackground(any_color_you_want);
listener.onButtonRemove(v, position);
}
}
});
}
In this example, I was trying to do a checkable list.
Try this way
Boolean flag=false;
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id){
if(flag==false){
parent.getChildAt(position).setBackgroundColor(Color.GREEN);
flag=true;
}
else{
parent.getChildAt(position).setBackgroundColor(Color.RED);
flag=false;
}
}
});
Just add an attribute variable in your Listener. Like this:
listView.setOnItemClickListener(new OnItemClickListener(){
private Set<Integer> hasClickedSet = new HashSet<Integer>();
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id){
if (hasClickedSet.contains(position)){
v.setBackgroundColor(Color.BLACK);
} else {
v.setBackgroundColor(Color.GREEN);
hasClickedSet.add(position);
}
}
and you need not calling parent.getChildAt(position). Just use the 'v' parameter.
========EDIT============
ok, try this:
listView.setOnItemClickListener(new OnItemClickListener(){
private SparseArray<Boolean> hasClicked = new SparseArray<Boolean>();
#Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id){
if (hasClicked.get(position, false)){
v.setBackgroundColor(Color.RED);
hasClicked.put(position, false);
} else {
v.setBackgroundColor(Color.GREEN);
hasClicked.put(position, true);
}
}
You can track n number of click
int count = 0; // define globally
view.clickListener(){
count++;
if (count==1)
{
// first click
}
else{
// not first click
}
}
Try this way,hope this will help you to solve your problem.
Take HashMap which hold all position and Boolean for is click or not :
HashMap<Integer,Boolean> clickMap = new HashMap<Integer, Boolean>();
Check HashMap contain this position mean this position item clicked base on change background as well Boolean value in HashMap :
if(clickMap.containsKey(position) && clickMap.get(position)){
parent.getChildAt(position).setBackgroundColor(Color.orignal);
clickMap.put(position,false);
}else{
clickMap.put(position,true);
parent.getChildAt(position).setBackgroundColor(Color.GREEN);
}
I have a ListView and when I scroll down or up I can detect this using this solution:
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) { }
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(listView.getFirstVisiblePosition() == 0)
getActionBar().show();
else
getActionBar().hide();
}
});
This way works and the ActionBar shows when the scroll comes back to the first item of the list. I would ALSO like to show the ActionBar again when I begin to scroll up within the list.
EDIT:
this is the last way and it works not always.. sometimes the actionbar disappears and never comes back:
private int mPreviousFirst;
...
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) { }
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(prevVisibleItem != firstVisibleItem){
if (firstVisibleItem < mPreviousFirst)
getActionBar().show();
else
getActionBar().hide();
mPreviousFirst = firstVisibleItem;
}
}
});
i'm close but something goes wrong :(
You can do so by saving the previous first visible position each time it changes:
private int mPreviousFirst;
...
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) { }
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (firstVisibleItem < mPreviousFirst) {
getActionBar().show();
} else if (firstVisibleItem > mPreviousFirst) {
getActionBar().hide();
}
mPreviousFirst = firstVisibleItem;
}
});
You can try manipulating the following onScrollChanged within your view,
onScrollChanged
which keeps track of the current and previous states of a scroll. If you are going up, then you could do the same behavior.
Something similar to the following would achieve what you are asking:
#Override
protected void onScrollChanged (int currentHorizontalPosition, int currentVerticalPosition, int oldHorizontalPosition, int oldVerticalPosition)
{
if(currentVerticalPosition < oldVerticalPosition && positionDifference > threshold)
{
getActionBar().show();
}
else //condition for downscroll or other actions.
{
getActionBar().hide();
}
}
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
int mLastFirstVisibleItem = 0;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) { }
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view.getId() == mListView.getId()) {
final int currentFirstVisibleItem = mListView.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
// getSherlockActivity().getSupportActionBar().hide();
activity.hideActionBar();
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
// getSherlockActivity().getSupportActionBar().show();
activity.showActionBar();
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
});
this works for me on kitkat
I have this code:
hubSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
final MediaPlayer mp2 = MediaPlayer.create(Textbox.this, R.raw.hero);
mp2.start();
}
public void onNothingSelected(AdapterView<?> parentView) {
}
});
(The code basically runs when a new item is selected of a spinner and then plays a song, which later will be a variable based on what was picked, but I'm fine as it is for now).
Problem: I want to be able to use 'mp2' out of this public void, (I want a button which pauses it). How can I do this?
Move the variable mp2 to the instance of the parent class. This will keep a running reference to it which you can interact with whenever you please. You will need to remove the final qualifier though if MediaPlayer.create(...) will be called more than once and stored.
edit:
I was referring to something along the lines of this:
class SomeClass {
private MediaPlayer mp2 = null;
private void whateverFunctionYouAreIn() {
hubSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
SomeClass.this.mp2 = MediaPlayer.create(Textbox.this, R.raw.hero);
SomeClass.this.mp2.start();
}
public void onNothingSelected(AdapterView<?> parentView) {}
});
//TODO: put this in an onClickListener:
if (this.mp2 != null) {
this.mp2.pause();
}
}
}
I'm not sure what happens when you call MediaPlayer.create(Textbox.this, R.raw.hero), but assuming it has no immediate effects, you can just create the object outside of the listener.
Edit1: OK, then how about this?
MediaPlayer currentPlayer;
methodA()
{
hubSpinner.setOnItemSelectedListener(new OnItemSelectedListener()
{
public void onItemSelected(AdapterView<?> parentView,
View selectedItemView, int position, long id)
{
MediaPlayer mp2 = MediaPlayer.create(Textbox.this, R.raw.hero);
mp2.start();
setPlayer(mp2);
}
public void onNothingSelected(AdapterView<?> parentView) {
}
});
setMediaPlayer(MediaPlayer player)
{
currentPlayer = player;
}