I'm fairly new to the android platform and I overrode a CustomView's onDraw method with canvas.drawPath(path, paint) in order to produce drawings on the page.
However, I attempted to save this drawing to be used in another activity (final page) where multiple saved drawings can be displayed side by side, but I ran into frequent issues with no images appearing on my final page.
I was wondering how to handle this scenario. I read posts on SO about storing the drawing as a bitmap and reconstructing this later. So I used Convert view to bitmap on Android
public static Bitmap getBitmapFromView(View view) { ...
to extract a bitmap from my custom view containing the original drawing. Then, I created a canvas with the extracted bit map using
Bitmap map = getBitmapFromView(cView);
Canvas canvas = new Canvas(map);
and appended this canvas to a static list of Canvas objects.
In the activity used to display all drawings side by side, I had
RelativeLayout layout = (RelativeLayout) findViewById(R.id.activity_display);
for (int x=0; x < canvasList.size(); x++) {
Canvas currCanvas = canvasList.get(x)
View view = new View(this);
view.draw(currCanvas);
layout.addView(canvasList.get(x));
}
There is definitely something wrong with my approach, but I have failed to identify it.
So, have you tried displaying the bitmapRecieved in getBitmapFromView to verify that it works?
Then the next step would be to store it in a static class, and try to display it in another acitvity to ensure storing works as planned.
And on your last Code Snippet: your are creating a View, drawing onto its canvas and then adding the canvas to the layout? without further research to logical thing to do, seems to add the view to the layout or am i wrong?
Try using Bundle savedInstance. Instead of using canvasList, I will use a bitmap array:
#Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
state.putSerializable("starttime", startTime);
for(int i=0; i<bmp.size; i++){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp[i].compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
state.putByteArray("img"+i, byte[] value);
}
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
if ((bundle != null)
for(int i=0; bundle.getByteArray("img"+i) != null; i++) {
//Load byteArray back to bitmap array
}
}
I haven't tested the code, so there might be some issues. There is a lot of ways to achieve what you want, you can also save in a drawable or save as a picture at user's client.
Good luck, let me know if it works!
Related
This is an often asked question but I just can't find the right solution for my case. I'm working on a news app and I want to fullscreen images in the news content when the user clicks the image, and another click will return to the news page. In my code, I have an ArrayList of data with the type of image or text, this method will go through the ArrayList and dynamically add an image view or text view according to the type. The code is like
public void setText(ArrayList<HashMap<String, String>> datas) {
for (HashMap<String, String> hashMap : datas) {
String type = hashMap.get("type");
if (type.equals("image")) {
int imagewidth = mTypedArray.getDimensionPixelOffset(R.styleable.customTextView_image_width, 100);
int imageheight = mTypedArray.getDimensionPixelOffset(R.styleable.customTextView_image_height, 100);
imageView = new ImageView(mContext);
params = new LayoutParams(imagewidth, imageheight);
params.gravity = Gravity.CENTER_HORIZONTAL;
imageView.setLayoutParams(params);
addView(imageView);
DownloadPicTask task = new DownloadPicTask(imageView, handler);
task.execute(hashMap.get("value"));
} else {
//Add some text view
}
I have tried simply changing the image to FIT_XY on click, but only the last spawned image enlarged, and I don't want other content to still remain around the image.
Perhaps I should set an onclick listener to each image, then create a new fullscreen activity? But I'm new to android and I don't know how to pass the image data correctly to fullscreen activity, and how to make another click back to the original content.
I am currently using the code here, albeit heavily modified to suit my needs
https://stackoverflow.com/a/28186621/4541217
such as I need to take an image from the camera as well as select from the gallery. I am also zooming the image.
This all works nicely, except for one issue. I lose things when I rotate the device.
I have
bTemp = null;
if(getLastNonConfigurationInstance() != null) {
bTemp = getLastNonConfigurationInstance();
}
in my onCreate, and an override...
#Override
#Deprecated
public Object onRetainNonConfigurationInstance() {
return bTemp;
}
I can make this return the image but I lose all of my stroke information.
From the example, I have tried saving the Uri, the alteredBitmap, the bitmap and the choosenImageView. However, none of these are working. If I take a photo, scribble on it, then before doing anything else, using the alteredBitmap, if I rotate, then I get the first set of strokes. However, nothing after that.
Can anyone help me to keep my stroke information on rotate please?
Learn about the activity lifecycle.
You need to override functions like onPause, onResume, and use the savedInstanceState.
I managed to work it out eventually, so for anyone else that is trying to do the same, here is what I did.
Following on from the example link in my opening post, in order to make it stick while rotating...
in the onRetainNonConfigurationInstance, keep the alteredBitmap. (This is in the Activity)
#Override
#Deprecated
public Object onRetainNonConfigurationInstance() {
return alteredBitmap;
}
then, in the onCreate of the activity...
if(getLastNonConfigurationInstance() != null) {
bmp = (Bitmap)getLastNonConfigurationInstance();
alteredBitmap = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
choosenImageView.setNewImage(alteredBitmap, bmp);
}
notice that the "bmp" is what was sent from alteredBitmap, and now alteredBitmap is the new image. This is then passed into the setNewImage in the DrawableImageView.
I'm new to Android and I have 9 ImageViews that I want to make transparent. I'm using AlphaAnimation to make them fade. They fade indeed but I want to make them fade one by one. Unfortunately they all fade together and I don't know why.
I tried to use various ways to do it(including CountDownTimer,Thread.sleep(),new Handler().postDelayed()), but there is no change. All of the ImageViews fade simultaneously, not one by one. I know they are capable of doing it because animation on one works, but iteration throught list of this views ends up with all of them being animated at the same time.
Important methods(I guess):
private void fadeImageTiles(List<ImageView> ivs) {
Collections.shuffle(ivs);
for (ImageView iv : ivs) {
//maybe there's problem with iteration?
gradientFade(iv);
}
}
private void gradientFade(ImageView iv){
AlphaAnimation animation = new AlphaAnimation(1f,0f);
animation.setDuration(2000);
iv.startAnimation(animation);
iv.setVisibility(View.INVISIBLE);
}
Final effect is to make them fade randomly revealing image behind
You can use void setStartOffset (long startOffset) to specify after how many milliseconds the view should be animated.
For example:
private void fadeImageTiles(List<ImageView> ivs) {
Collections.shuffle(ivs);
for (int i = 0; i < ivs.size(); i++) {
//maybe there's problem with iteration?
gradientFade(ivs.get(i), i);
}
}
private void gradientFade(ImageView iv, int index){
AlphaAnimation animation = new AlphaAnimation(1f,0f);
animation.setDuration(2000);
animation.setStartOffset(index * 500);
iv.startAnimation(animation);
iv.setVisibility(View.INVISIBLE);
}
Or you can use ViewPropertyAnimator without writing much code. Replace your code inside your gradientFade method with this:
iv.animate().alpha(0).setDuration(2000).setStartDelay(index * 500);
Given a list of true-color full frames in BufferedImage and a list of frame durations, how can I create an Image losslessly, that when put on a JLabel, will animate?
From what I can find, I could create an ImageWriter wrapping a ByteArrayOutputStream, write IIOImage frames to it, then Toolkit.getDefaultToolkit().createImage the stream into a ToolkitImage.
There are two problems with this attempt.
ImageWriter can only be instantiated with one of the known image encoders, and there is none for a lossless true-color animated image format (e.g. MNG),
It encodes (compresses) the image, then decompresses it again, becoming an unnecessary performance hazard.
[Edit]
Some more concise constraints and requirements. Please don't come up with anything that bends these rules.
What I don't want:
Making an animation thread and painting/updating each frame of the animation myself,
Using any kind of 3rd party library,
Borrowing any external process, for example a web browser,
Display it in some kind of video player object or 3D-accelerated scene (OpenGL/etc),
Work directly with classes from the sun.* packages
What I do want:
Frame size can be as large as monitor size. Please don't worry about performance. I'll worry about that. You'll just worry about correctness.
Frames all have the same size,
an Image subclass. I should be able to draw the image like g.drawImage(ani, 0, 0, this) and it would animate, or wrap it in an ImageIcon and display it on a JLabel/JButton/etc and it would animate,
Each frame can each have a different delay, from 10ms up to a second,
Animation can loop or can end, and this is defined once per animation (just like GIF),
I can use anything packaged with Oracle Java 8 (e.g. JavaFX),
Whatever happens, it should integrate with SWING
Optional:
Frames can have transparency. If needed, I can opaquify my images beforehand as the animation will be shown on a known background (single color) anyway.
I don't care if I have to subclass Image myself and add an animation thread in there that will cooperate with the ImageObserver, or write my own InputStreamImageSource, but I don't know how.
If I can somehow display a JavaFX scene with some HTML and CSS code that animates my images, then that's fine too. BUT as long as it's all encapsulated in a single SWING-compatible object that I can pass around.
You're right that ImageIO isn't an option, as the only animated format for which support is guaranteed is GIF.
You say you don't want to make an animation thread, but what about a JavaFX Animation object, like a Timeline?
public JComponent createAnimationComponent(List<BufferedImage> images,
List<Long> durations) {
Objects.requireNonNull(images, "Image list cannot be null");
Objects.requireNonNull(durations, "Duration list cannot be null");
if (new ArrayList<Object>(images).contains(null)) {
throw new IllegalArgumentException("Null image not permitted");
}
if (new ArrayList<Object>(durations).contains(null)) {
throw new IllegalArgumentException("Null duration not permitted");
}
int count = images.size();
if (count != durations.size()) {
throw new IllegalArgumentException(
"Lists must have the same number of elements");
}
ImageView view = new ImageView();
ObjectProperty<Image> imageProperty = view.imageProperty();
Rectangle imageSize = new Rectangle();
KeyFrame[] frames = new KeyFrame[count];
long time = 0;
for (int i = 0; i < count; i++) {
Duration duration = Duration.millis(time);
time += durations.get(i);
BufferedImage bufImg = images.get(i);
imageSize.add(bufImg.getWidth(), bufImg.getHeight());
Image image = SwingFXUtils.toFXImage(bufImg, null);
KeyValue imageValue = new KeyValue(imageProperty, image,
Interpolator.DISCRETE);
frames[i] = new KeyFrame(duration, imageValue);
}
Timeline timeline = new Timeline(frames);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(new Group(view)));
panel.setPreferredSize(imageSize.getSize());
return panel;
}
(I don't know why it's necessary to set the JFXPanel's preferred size explicitly, but it is. Probably a bug.)
Note that, like all JavaFX code, it has to be run in the JavaFX Application Thread. If you're using it from a Swing application, you can do something like this:
public JComponent createAnimationComponentFromAWTThread(
final List<BufferedImage> images,
final List<Long> durations)
throws InterruptedException {
final JComponent[] componentHolder = { null };
Platform.runLater(new Runnable() {
#Override
public void run() {
synchronized (componentHolder) {
componentHolder[0] =
createAnimationComponent(images, durations);
componentHolder.notifyAll();
}
}
});
synchronized (componentHolder) {
while (componentHolder[0] == null) {
componentHolder.wait();
}
return componentHolder[0];
}
}
But that's still not quite enough. You first have to initialize JavaFX by calling Application.launch, either with an explicit method call, or implicitly by specifying your Application subclass as the main class.
something like
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(getImageForCurrentTime(), 3, 4, this);
}
I am developing a chat application, where i have implemented image sending. So without the images everything works fine. But now I'm facing issues in displaying the images that are sent / received. For that what i've done is downloaded the image first and saved it in external storage. My idea was simple fetch the bitmap set it in an imageview on the getView method. Nothing wrong with fetching the image and displaying but listview ruins everthing.
This is my code-
ListView getView()
ViewHolder holder = null;
if(convertView == null ){
convertView = inflater.inflate(R.layout.chat_list, null);
holder = new ViewHolder();
holder.mMsg = (TextView) convertView.findViewById(R.id.text);
holder.mDate = (TextView) convertView.findViewById(R.id.text1);
holder.mLinLay=(LinearLayout) convertView.findViewById(R.id.linSide1);
holder.mRelLay=(RelativeLayout) convertView.findViewById(R.id.relSide);
holder.img=(ImageView)convertView.findViewById(R.id.image);
holder.progCircle=(ProgressBar)convertView.findViewById(R.id.pbHeaderProgress);
convertView.setTag(holder);
} else{
holder = (ViewHolder) convertView.getTag();
}
holder.position=position;
holder.img.setTag(position+"img");
HashMap<String, String> hm = list.get(position);
String date=hm.get("date");
long l = Long.parseLong(date);
long unixTime = System.currentTimeMillis();
CharSequence datedate=DateUtils.getRelativeTimeSpanString(l, unixTime, 1);
String status = hm.get("status");
String who = hm.get("who");
if(hm.get("message").contains("[[pic_message]]"))
{
String picName=hm.get("message").replace("[[pic_message]]", "");
final String picPath1=Environment.getExternalStorageDirectory()+"/App/sent_pics/"+picName+".jpg";
File file = new File(picPath);
if(file.exists())
{
new AsyncTask<ViewHolder, Void, Bitmap>() {
private ViewHolder v;
Bitmap bm;
#Override
protected Bitmap doInBackground(ViewHolder... params) {
v = params[0];
bm = decodeSampledBitmapFromResource(picPath1,100,100);
return bm;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.img.getTag().equals(position+"img")) {
v.img.setImageBitmap(result);
}
}
}.execute(holder);
}else{
holder.img.setImageBitmap(null);
}
}else{
holder.img.setImageBitmap(null);
Spannable msg =getSmiledText(getApplicationContext(),hm.get("message"));
holder.mMsg.setText(msg);
}
holder.mDate.setText(datedate);
return convertView;
What the problem is-
The listview shows up, but the images are displayed at random listview items. I searched a lot for this kind of problem, tried them but still no luck. The asyntask code is from Google- Use A Background Thread.. As you see from above i didn't set any holder.mMsg.setText(msg); but still whenever the images are shown they are accompanied by a msg, which is not desirable. Also the scrolling is kindda laggy when the images are shown. I have seen many messaging apps, they all run so smoothly, and the items are never mixed up. So what do they use.Also, while googling this problem, one answer suggested that we should remove the converView if else and then everything will be fine. But that will have performance issues which i don't want.
So my question is what should i do? How do the chat apps like, hangouts,bbm,whatsapp achieve this so smoothly?
EDIT
The pictures are displayed with no problem but the main problem is that they are diplayed at random orders.
Like this- i scroll till the image message, they are displayed. Then I again scroll and now the image is displayed in other listview item. My guess is that the same view(which originallly holds the image) is now being used by other, but the image stays there. How can i fix that ? And make the listview smooth (even with images) like the IM apps?
It's because Android ListView reuses Views, so while your Asynctask execute the donInBackground the user is already doing something else and you code don't know anything about it. (So it will put the image)
I would use Picasso to avoid the problem and let someone else handle your problem.
I handled this problem in my application (a contact phone) using:
A cache, to avoid to send multiple requests for the same image even when i already have in memory his image
I send a WeakReference<> to the Asynctask so when it's done check if the ImageView is still valid and put the bitmap inside it. Update the cache.
(Specific to me) Since sometimes i could happen that a contact don't have a photo i just created another List with all users without a photo (so i don't try to download it)
Everytime a .getview is called check if the image is inside the cache and if it's inside the cache put directly it, or start the asynctask.
Bitmap bitmap = cache.get(number);
if (bitmap == null) {
Log.d(TAG, "I will download image for " + name + ".");
// i set a dummy image to avoid to show the wrong picture
holder.contactPhoto.setImageResource(R.drawable.ic_action_person);
loadUserProfilePhoto(number, holder.contactPhoto);
}
else {
// show it directly
Log.d(TAG, "I already have " + name + " in cache.");
holder.contactPhoto.setImageBitmap(bitmap);
}
If you see, it could happen that it shows another picture instead of the user-photo while it's still loading it so i set a dummy picture instead of the old photo.
It's not the best implementation, but it's to show you that it could be very hard to implement it by yourself and do it right.
Read Picasso documentation and see how it could implement it in your code (it's simple, it's just one line.)
The issue is with listview which continuously keep refreshing it.
So your all code is fine, but just set default image in holder.img instead of making it null holder.img.setImageBitmap(null);
And may be in async too, you need set default_image while your image is being loaded.
If you want display images on the fly I think you should use SurfaceView. It is a lot faster and has a higher refresh rate. I made a video streaming application where I tried to display each video frame on the ImageView but that failed. But when I tried out SurfaceVIew it worked out fine.