i have a problem with setOnMouseEntered on JavaFx for ImageViews.
I'm trying to change the brightness of ImageViews placed in a Listview with ColorAdjust. The effect itself works for ImageViews that are not in the ListView.
I guess that only the ListView triggers the setOnMouseEntered, but not the ImageViews, which is my goal.
The same problem is caused by the hover effects of the ImageViews, which are not triggered as soon as they are in a ListView.
fxml:
<ListView fx:id="cardsView" />
java-Code:
#FXML private ListView<ImageView> cardsView;
private ObservableMap<ImageView, Card> hCards;
#FXML
public void initialize() {
hCards= FXCollections.observableHashMap();
cardsView.getItems().setAll(hCards.keySet());
hCards.addListener(
(MapChangeListener<ImageView, Card>)
change -> {
cardsView.getItems().removeAll(change.getKey());
if (change.wasAdded()) {
cardsView.getItems().add(change.getKey());
}
});
}
Later, for each of these ImageViews will be added:
private void addLightEffectOnMouseEntered(ImageView imageView) {
imageView.setOnMouseEntered(
t -> {
ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setBrightness(0.4);
imageView.setEffect(colorAdjust);
});
}
While debugging I figured out that things like css and the setOnMouseEntered are added correctly. So it seems to be somehow blocked by the ListView that the ChildNodes get the setOnMouseEntered or Hover effect instead of the ListView
Your problem is basically the same as this one: Adding EventHandler To ImageView contained in a Label. All Cell specializations, including ListCell, inherit from Labeled and all their default skins inherit from LabeledSkinBase, which is the source of your problem. As a fix to a bug (see other Q&A) when an ImageView is used as the graphic of a Labeled it is set to be mouse-transparent. Since the the ImageView is mouse-transparent your MOUSE_ENTERED handler can never be invoked for obvious reasons.
If you're not aware, the default cell factory of ListView returns a ListCell implementation that, when the item is an instance of Node, sets the graphic of the cell to the item. An easy fix is to use your own ListCell implementation that wraps the ImageView in another node, such as Pane. Here's an example:
listView.setCellFactory(lv -> new ListCell<>() {
private final Pane imageViewContainer = new Pane();
#Override
protected void updateItem(ImageView item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageViewContainer.getChildren().clear();
setGraphic(null);
} else {
imageViewContainer.getChildren().setAll(item);
setGraphic(imageViewContainer);
}
}
});
This will prevent the ImageView from becoming mouse-transparent.
As a side note, it's typically not a good idea to use GUI objects (e.g. ImageView) as the model item of a ListView (or any other virtualized control). In this case, it may be an even worse idea since this set-up encourages holding every Image related to your application in memory simultaneously. Depending on how many images there are, as well as how large those images are, this can easily lead to an OutOfMemoryError or at least consume an unnecessary amount of your users' RAM.
You may want to consider using Card as the model item combined with a memory-constrained cache of Image objects (see WeakReference / SoftReference, though you could also look for a third-party caching library). The Card class could hold the location of its associated image or the cache could derive the location based on the state of the Card.
You would still use an ImageView as the graphic of your ListCell, however, so you would still need to use the workaround mentioned above. What using a memory-constrained cache helps with is that, if a Card isn't being displayed in a ListCell then its associated Image possibly becomes eligible for garbage collection, thus reducing the memory demands of your application.
The cache also allows you to use the same Image everywhere in your application (the same Image can be shared between multiple ImageViews) and means you don't always load a new Image when a particular one is needed (as it could still be in memory when requested). In other words, the typical functionality provided by any cache.
Related
I want to create a dynamic intermittent progress bar (as illustrated in the image below), that will change its intervals depending on the user's choice, in Android Studio.
The idea is that the user will choose how many times he wishes to do a behavior, and then the bar will fragment accordingly. Then each time they do the behavior, the bar will color a step increment, as shown in the image below.
Intermittent Progress Bar
I am looking for some general guidance on how to do it, since I am new to this.
I have thought of 3 ways to do this:
Have a ton of png. drawables or vectors for each case, and use one accordingly in an Image View. (seems kind of stupid to me)
Create as many views as are the intervals, and then change the view colors accordingly (in this case there will be a problem with the dynamic part of it i.e. interval variability)
Customize somehow a horizontal ProgressBar to do this automatically.
I have searched the internet for the third way which is the most elegant to me, but cant find an answer.
Thank you for your time.
This is actually trivial to obtain using the current ProgressBar APIs.
Depending on the number of tasks required and number of tasks done, you can update a ProgressBar using setMax and setProgress
progressBar.setMax(totalTasks);
progressBar.setProgress(tasksDone);
This will cover a fraction tasksDone / totalTasks in the progressBar.
Edit: Based on the scenario highlighted in the comments, you can simply use multiple views in a LinearLayout.
You can use a LinearLayout with the background of the partition color you want.
<LinearLayout
....
android:orientation="horizontal"
android:background="#color/partition"
....
/>
And then simply add the child views programmatically with equal weights (and a horizontal margin):-
for(task in tasks) {
val progressBar = View(this)
progressBar.marginEnd = gapRequired
// customize your view here: Set background, shape etc.
// Set the width to 0 and weight to 1f
val params = LinearLayout.LayoutParams(0, height, 1f)
linearLayout.addView(view, index, params)
}
Later to modify an individual view (fragment of progress bar):-
val view = linearLayout.getChildAt(index)
// Modify the view as needed
To remove a view from the layout:-
linearLayout.removeViewAt(index)
Or if you have the view referenced:-
linearLayout.removeView(view)
You can also remove all views (if you need to reset the entire progress bar for some reason) using linearLayout.removeAllViews()
You might want to use RecyclerView with an adapter if you are expecting a lot of these fragments in your progressBar.
Drawing texts on a static layout like textview, and the texts are an item on a recyclerview. Normally, the textview has getSelectionStart() and getSelectionEnd() functions but how to get the selection positions using a static layout?
StaticLayout should not be used in most of the cases. As stated in Android Developer Guides: "You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText() directly."
It is a very low-level component for drawing text. It is mostly used in specific situations e.g. when you want to draw text on bitmaps, or measure the size of the TextView before it is created, etc.
Therefore, it doesn't contain any API for handling text selection. That is implemented in TextView (which also uses StaticLayout under the hood).
Using just TextViews can in no situation affect performance of the app. Even with huge amounts of data, if you properly recycle TextViews in your RecyclerView using the mandatory ViewHolder, you will never have more TextViews than visible on the screen, only the text content will change as they get recycled/re-inserted, so there's no performance gain in avoiding them.
Hello i am writing a app which uses many customView(parent as FrameLayout holding activity's context).
In the app there is a recyclerView it's holder is having N(significantly large number) of view's(Custom).
As the number of View's are dynamically added (i am testing for 10k+ view's).
in recycler View i am not doing anything special but using coroutine's so that rendering of 10k view will not block the Main Thread
But due to large number's of View's in each RecyclerView Holder it's Filling up the heap memory of the app very fast as i scroll and give's OOM(Out of Memory) Crash.
To solve this issue i read and found I can use weak reference's to the
View.But i have not found any example or reference to this approach.So
i am not sure if using WeakReference on the Android View is a good
solution or not.
Code and Demo Project Reference Below.
RecyclerView Adapter :
override fun onBindViewHolder(holder: ViewPageHolder, position: Int) {
val slideViewMultipleCanvas = SlideViewMultipleCanvas(context)
holder.slideViewItemHolder.addView(slideViewMultipleCanvas)
holder.positionHolder = position
val childJob = launch {
//this is where 10k+ view's are created
slideViewMultipleCanvas.setNumOfObjects(numOfItemsInViewPage)
slideViewMultipleCanvas.startJob()
}
map[position] = childJob
}
CustomView :
...
...
suspend fun setNumOfObjects(numberOfObjects: Int) {
withContext(Dispatchers.Default) {
for (i in 0 until numberOfObjects) {
// To solve OOM as number of view here in heap are >10k
listOfObject.add(WeakReference(ShapeView(context)))
}
}
}
...
...
private fun addViewInScope(): Boolean {
for (shapeView in listOfObject) {
shapeView.get()?.let {
addView(it)
}
}
return true
}
....
....
After Using WeakReference from my testing i have not faced any issue(NO OOM's) till now but still i get Lag's whenever GC(Garbage Collector) kick's in and collect all the weak reference.
My Main concerns are
Is There any other way to handle creation of huge number of View without using WeakReference?
Is there any issue's if i use weak reference for Views?
how to decrease GC lag?(even using WeakReference i am not getting smooth scroll)
P.S. : I am already handling:
null cases if GC collect's the weak referenced View's
cancelling coroutine job's when view is recycled in RecyclerView.(can i release the weak Referred view also of this ViewHolder also???)
Look into RecyclerViewPools, you should be able to allocate a specific view (if the dynamically created ones are the same view) using the itemType and have the recyclerView reuse those views also
The main lag isn’t from Weakreferences, but purely the need to generate so many views so quickly, if you could keep a cached list of views to reuse (kind of like the recyclerViewPool) then that would reduce the inflating lag
Only issue if you’ve got so many views it causes OOM, is it possible to combine the views into a generic view with multiple functionality (less physical views, but more logic and complexity in each to cater for being used under multiple situations
This would allow you to reuse the same view more times, meaning less caching/inflating, you’ll just need to make sure the code is performing efficiently
I want to load images I have in my recyclerview after 350ms and I think I'm using wrong method for that. This is my code:
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.songView.setText(objects_.get(position).getAlbum());
holder.artistView.setText(objects_.get(position).getArtist());
holder.cover.setImageDrawable(context.getResources().getDrawable(R.drawable.song));
if(holder.r!=null){
handler.removeCallbacks(holder.r);
}
holder.r = new Runnable() {
#Override
public void run() {
Drawable img = Drawable.createFromPath(objects_.get(position).getCover());
if (img != null) {
holder.cover.setImageDrawable(img);
Bitmap bitmap = ((BitmapDrawable) img).getBitmap();
Palette palette = Palette.from(bitmap).generate();
Palette.Swatch p = palette.getVibrantSwatch();
if (p != null) {
holder.albumholder.setBackgroundColor(p.getRgb());
}
}
}
};
handler.postDelayed(holder.r,300);
}
But I have a problem with this. when I fast scroll recyclerview images of previous items loads at first then changes to new items picture. You can see result in GIF from this link:
http://8pic.ir/images/nkaaeqdvigqy4c6g2h5n.gif
what can I do to fix it?
I don't understand why do you need this 350ms delay but if you want to do it try some other approach:
Your problem is linked to the fact that RecyclerView recycles (suprise...) item views instead of creating new. That means that you will see previously load image, and if you have posted delayed task (handler.postDelayed(...)) it will be executed event if view was recycled, so wrong image can be loaded for particular list item.
General problem is that you're doing to much work in your onBindViewHolder. You should try to reduce computations here, or at least try to move them to some separate thread (handler is using this same thread it was created - in this case the UI thread).
Create handler inside view holder instead of inside your adapter.
Set some placeholder as an image
clear tasks (messages) currently waiting to be executed:
holder.handler.removeCallbacksAndMessages(null);
post load task (handler.postDelayed(...))
It's also possible that all you need is some nice image loading library like Picasso.
As we know recycler view reuse same view during scroll so it is displaying older images while you lazy load for some moments and after that it will update your imageview.
Solution is simply reset your imageview to default( ie white background or default image) state before lazyload .
You should avoid setimageresource() ,instead use setimagedrawable()
setImageResource Vs setDrawable
Instead of using the runnable for loading images, use AsyncTask. You'll need to execute a separate AsyncTask for each image. This AsyncTask will be saved as a WeakReference inside the drawable object which will be set in the respective ImageView.
WeakReferences are used for mapping purposes. The advantage of using WeakReferences is that the entries can be removed from the memory as soon as they are not required by your app. They will be removed by the Garbage Collector. We need to use the WeakReferences because there can be a large number of AsyncTasks getting executed (equal to the number of items present in the RecyclerView) at the same time and Android system or your app will not be able to identify which AsyncTask belongs to which ImageView without these references.
Now, as the drawable is set in the ImageView, it will contain the WeakReference to its respective AsyncTask. This AsyncTask will process the respective bitmap or drawable to be set on the ImageView and all this will be done off the UI thread.
In order to set the AsyncTask in the drawable object, you'll need to create a custom drawable class which will work as a Drawable object but will have the benefit of attaching an AsyncTask to it.
This Drawable object and AsyncTask will take care of loading the images.
The complete explanation and code for this concept has been provided on Android Developers website. Visit the link: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
I am currently using a custom adapter to display an ImageView and two TextViews per row in a ListView.
Within the overridden getView for the adapter, I have this code for the ImageView:
final ImageView img = (ImageView) view.findViewById(R.id.rowImg);
new Thread(new Runnable() {
public void run() {
final BitmapDrawable b = downloadAvatar(urlToDownload);
img.post(new Runnable() {
public void run() {
img.setImageDrawable(b);
}
});
}
}).start();
The downloadAvatar method basically just uses AndroidHttpClient and HttpGet. The above method works, but my question is how do I optimize it? Scrolling is choppy; I know it's probably calling getView() and downloading the image each and every time it enters the viewable area. Any tips?
Please see below Lazy Loading listview's source link and universal image loader example for that, it may help you.
Lazy Loading Listview
Android Universal Image Loader
In your case, you'll need to do what's called "lazy load of images" which will cache those images once downloaded. Check this out: Lazy load of images in ListView
Here's an option:
Inside getView check if you have the current image downloaded. If it isn't downloaded yet - download it and store it in a directory of your choice. If it is present in that directory - just load it.
Depending on the image size and count you can also make some kind of cache to store the image objects required for the visible (and maybe the closest invisible above and below the visible) items.
I would suggest to use the ignition-library which has an component called RemoteImageView, which will load asynchronously url images, and the library will also keep a cache of those images, so they are not downloaded every time you re-create your ListView rows.
I use it, and I find it very usefull and robust.