invalidate not redrawing view on api 25 - android

I have a custom view that extends LinearLayout.
The view looks like progress bar with a little icon that moves on every click.
the updating method is :
public void setPointerOffset(int mPointerOffset) {
this.mPointerOffset = mPointerOffset;
updateSlider();
invalidate();
requestLayout();
}
private void updateSlider() {
PercentFrameLayout.LayoutParams params = (PercentFrameLayout.LayoutParams) mPointer.getLayoutParams();
PercentLayoutHelper.PercentLayoutInfo info = params.getPercentLayoutInfo();
if (mPointerOffset < MIN_OFFSET)
mPointerOffset = MIN_OFFSET;
if (mPointerOffset > MAX_OFFSET)
mPointerOffset = MAX_OFFSET;
float percent = mPointerOffset * 0.01f;
info.startMarginPercent = percent;
}
This method is fired up from onClickListener.
This is working great in low api like 17, but on the lest on (25) it doesn't working at all.

setting layout params to "mPointer" semms to fix it.
private void updateSlider() {
PercentFrameLayout.LayoutParams params = (PercentFrameLayout.LayoutParams) mPointer.getLayoutParams();
PercentLayoutHelper.PercentLayoutInfo info = params.getPercentLayoutInfo();
if (mPointerOffset < MIN_OFFSET)
mPointerOffset = MIN_OFFSET;
if (mPointerOffset > MAX_OFFSET)
mPointerOffset = MAX_OFFSET;
float percent = mPointerOffset * 0.01f;
info.startMarginPercent = percent;
mPointer.setLayoutParams(params);
}

Related

Why setting windowLightStatusBar ain't working programmatically using WindowInsetsControllerCompat#setAppearanceLightStatusBars()

I'm trying to create a launcher app, but i had problem when trying to change windowLightStatusBar programmatically on API 30 and the same code works perfectly fine bellow that, i hope if someone can help me out:)
Here is the full class source code:
public final class AppDrawer extends BottomSheetBehavior.BottomSheetCallback implements OnApplyWindowInsetsListener {
private static boolean ALREADY_CREATED = false;
private AppCompatActivity activity;
private boolean dragging = false;
private float radius;
private int left, right, height, peekOver, oldState;
private Rect displayRect = new Rect();
private DisplayMetrics displayMetrics = new DisplayMetrics();
private Insets navigationBarInsets = Insets.of(0, 0, 0, 0), cutoutInsets = navigationBarInsets;
private WindowInsetsControllerCompat windowInsetsController;
private Configuration oldConfiguration;
private CardView cardView;
private FrameLayout frameLayout;
private BottomSheetBehavior<FrameLayout> bottomSheetBehavior;
public AppDrawer(AppCompatActivity activity){
if(ALREADY_CREATED) throw new IllegalArgumentException("The appDrawer object cannot be created more than once, clear the existing one from the memory first.");
this.activity = activity;
ALREADY_CREATED = true;
}
public void destroy(){
activity = null;
radius = 0.0f;
left = right = height = peekOver = oldState = 0;
displayRect = null;
displayMetrics = null;
navigationBarInsets = cutoutInsets = navigationBarInsets = null;
windowInsetsController = null;
oldConfiguration = null;
cardView = null;
frameLayout = null;
bottomSheetBehavior = null;
ALREADY_CREATED = false;
}
// WindowInsetControllerCompat is created and set from the activity that created this class
public void setWindowInsetsController(#NonNull WindowInsetsControllerCompat windowInsetsController) { this.windowInsetsController = windowInsetsController; }
public void initViews(View contentView){
oldConfiguration = activity.getResources().getConfiguration();
cardView = activity.findViewById(R.id.app_drawer_card_view);
frameLayout = activity.findViewById(R.id.app_drawer_container);
bottomSheetBehavior = BottomSheetBehavior.from(frameLayout);
bottomSheetBehavior.addBottomSheetCallback(this);
ViewCompat.setOnApplyWindowInsetsListener(contentView, this);
updateVars();
}
#Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
if(dragging) return insets;
cutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout());
navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
updateVars();
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) frameLayout.setPadding(left + navigationBarInsets.left, 0, right + navigationBarInsets.right, 0);
return insets;
}
public void setConfiguration(Configuration configuration) { this.oldConfiguration = configuration; }
private void updateWindowInsetsBars(int state){
// here is the one i used
if (state != BottomSheetBehavior.STATE_COLLAPSED && (oldConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_NO) {
windowInsetsController.setAppearanceLightNavigationBars(true);
windowInsetsController.setAppearanceLightStatusBars(true);
} else {
windowInsetsController.setAppearanceLightNavigationBars(false);
windowInsetsController.setAppearanceLightStatusBars(false);
}
// here is the one of #Zain answer
if (state != BottomSheetBehavior.STATE_COLLAPSED && (oldConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_NO) {
new WindowInsetsControllerCompat(activity.getWindow(), activity.getWindow().getDecorView()).setAppearanceLightStatusBars(true);
new WindowInsetsControllerCompat(activity.getWindow(), activity.getWindow().getDecorView()).setAppearanceLightNavigationBars(true);
} else {
new WindowInsetsControllerCompat(activity.getWindow(), activity.getWindow().getDecorView()).setAppearanceLightStatusBars(false);
new WindowInsetsControllerCompat(activity.getWindow(), activity.getWindow().getDecorView()).setAppearanceLightNavigationBars(false);
}
}
private void updateCardViewState(float slideOffset){
float a = 1.f - slideOffset;
cardView.setRadius(radius * a);
frameLayout.setPadding((int) ((left + navigationBarInsets.left) * a) , 0, (int) ((right + navigationBarInsets.right) * a), 0);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) cardView.getLayoutParams();
layoutParams.height = (int) ((displayRect.height() + navigationBarInsets.bottom + cutoutInsets.top + cutoutInsets.bottom) * slideOffset + height * a);
Log.d("AppDrawer", "card height: " + layoutParams.height + ", screen height: " + displayRect.height());
//cardView.setAlpha(Math.min(slideOffset * 1.1f, 1.0f));
cardView.setLayoutParams(layoutParams);
}
private void updateDisplayMetrics(){
WindowManager windowManager = this.activity.getWindowManager();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
displayRect.set(windowManager.getCurrentWindowMetrics().getBounds());
displayRect.bottom -= navigationBarInsets.bottom + cutoutInsets.top + cutoutInsets.bottom;
displayRect.right -= navigationBarInsets.right + cutoutInsets.left + cutoutInsets.right;
} else {
displayRect.top = 0;
displayRect.bottom = displayMetrics.heightPixels;
displayRect.left = 0;
displayRect.right = displayMetrics.widthPixels;
}
}
private void updateVars(){
updateDisplayMetrics();
peekOver = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, displayMetrics);
radius = radius == 0 ? cardView.getRadius() : radius;
left = left == 0 ? frameLayout.getPaddingLeft() : left;
right = right == 0 ? frameLayout.getPaddingRight() : right;
height = height == 0 ? cardView.getLayoutParams().height : height;
bottomSheetBehavior.setPeekHeight(height + navigationBarInsets.bottom + peekOver);
if(oldState != 0) updateCardViewState(oldState == BottomSheetBehavior.STATE_COLLAPSED ? 0.0f : 1.0f);
}
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
dragging = newState == BottomSheetBehavior.STATE_DRAGGING;
updateWindowInsetsBars(newState);
oldState = newState;
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) { updateCardViewState(slideOffset); }
}
Here you can see on API 30 that the navigationbar is white while the statusbar still black, even tho both are set to false but it doesn't effect the statusbar at all.
Screenshot-1 on API 30
Screenshot-2 on API 30
But here on API 29 (it works fine below that too) that they are both changes fine.
Screenshot-1 on API 29
Screenshot-2 on API 29
It is a platform bug.
https://issuetracker.google.com/issues/180881870
To fix this issue you should just update your androidx.core:core-ktx to version 1.8.0-alpha03 or higher.
Pls refer to api changes for details
https://developer.android.com/jetpack/androidx/releases/core#1.8.0-alpha03
I found the solution:
after a bit of testing different stuff i found what caused that problem.
/* make system bars transparent */
private void setup(){
Window window = getWindow();
window.requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
WindowCompat.setDecorFitsSystemWindows(window, false);
WindowInsetsControllerCompat windowInsetsController = new WindowInsetsControllerCompat(window, window.getDecorView());
windowInsetsController.show(WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.navigationBars() | WindowInsetsCompat.Type.systemBars());
appDrawer.setWindowInsetsController(windowInsetsController);
}
In that method setup() that makes the status/navigation bar transparent when activity calls onCreate() method, well everything is fine except one thing and that i didn't set window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); after doing so, the status bar changes just fine.
Here is the new code:
/* make system bars transparent */
private void setup(){
Window window = getWindow();
window.requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
// Adding it fix the problem.
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
WindowCompat.setDecorFitsSystemWindows(window, false);
WindowInsetsControllerCompat windowInsetsController = new WindowInsetsControllerCompat(window, window.getDecorView());
windowInsetsController.show(WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.navigationBars() | WindowInsetsCompat.Type.systemBars());
appDrawer.setWindowInsetsController(windowInsetsController);
}
Hopefully someone will find this useful, and doesn't make the same silly mistake as i did😅
UPDATE:
I found out what really caused the problem, is it adding:
<item name="android:windowLightStatusBar">true</item>
to the themes.xml file makes setting statusbar to light programmatically not working, i don't know what make that causes the problem but now that i know it, i don't have to use window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); since it is deprecated on API 30 and above as #Zain pointed out.
For me it's because of this:
<item name="android:statusBarColor">#color/transparent</item>
This is prevent me to make change to status bar text color programmatically! After I remove this, I can change status bar text color programmatically again! I hope this is useful for someone!

Android fragment blank

I have a fragment which has RelativeLayout and a ImageView.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/pagelayout"
android:layout_below="#layout/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#color/aqua_blue"
tools:context="MainActivityFragment">
<ImageView
android:id="#+id/pdfImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:adjustViewBounds="true"
android:scaleType="fitStart"/>
I render a pdfpage as an image and display in the image view in onViewCreated as follows.
pageLayout = (RelativeLayout) rootView.findViewById(R.id.pagelayout);
//Retain view references.
mImageView = (ImageView) rootView.findViewById(R.id.pdfImage);
// Show the first page by default.
mCurrentPageNum = pdfRenderer.getmCurrentPageNum();
if (mCurrentPageNum == 0) {
mCurrentPageNum = 1;
pdfRenderer.setmCurrentPageNum(mCurrentPageNum);
}
showPage(mCurrentPageNum, true, this.activity);
//get screen size
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
scrwidth = metrics.widthPixels;
scrheight = pdfRenderer.getPDFImage().getHeight();
pageControlsList = pdfRenderer.GetPageControls();
if (pageControlsList != null) {
if (pageControlsList.size() > 0) {
generateControls();
showPage(mCurrentPageNum, true, this.activity);
}
}
When the user swipe on the page, I navigate to next page or previous page. For that I am calling the following function written in fragment from Mainactivity.
public void Readpage(int index, boolean PdfReload, Activity activity)
{
//context=activity;
//pageLayout = (RelativeLayout) rootView.findViewById(R.id.pagelayout);
//mImageView = (ImageView) rootView.findViewById(R.id.pdfImage);
showPage(index, true, activity);
pageControlsList = pdfRenderer.GetPageControls();
if (pageControlsList != null) {
generateControls();
}
}
Generatecontrols method is called if there are any controls in the page and generated dynamically. The code is as follows (given only one control for sample).
public void generateControls() {
//iterate loop to create each control
for (Map.Entry<Integer, PageElement> ctrl : pageControlsList.entrySet()) {
Integer ctrlKey = ctrl.getKey();
PageElement pageField = ctrl.getValue();
String name = pageField.getName();
int type = pageField.getType();
int pdfw = (int) pdfRenderer.getPage_width();
int pdfh = (int) pdfRenderer.getPage_height();
//get dimensions of the control
int left = (int) pageField.getLeft();
int top = (int) pageField.getTop();
int right = (int) pageField.getRight();
int bottom = (int) pageField.getBottom();
int width = (int) pageField.getWidth();
int height = (int) pageField.getHeight();
//calculate dimensions w.r.t. screen size
int ctrlW = scrwidth * width / pdfw;
int ctrlH = scrheight * height / pdfh;
int ctrlLeft = (int) (scrwidth * left) / pdfw;
int ctrlTop = (int) (((scrheight * (pdfh - top)) / pdfh));
int ctrlRight = (int) (scrwidth * right) / pdfw;
int ctrlBottom = (int) (scrheight * bottom / pdfh);
//set dimensions of the control with layout parameters
RelativeLayout.LayoutParams ctrllp = new RelativeLayout.LayoutParams(ctrlW, ctrlH);
ctrllp.setMargins(ctrlLeft, ctrlTop, 0, 0);
//generate controls based on the type of control
if (type == 2) { //checkbox
CheckBox myChkBox = new CheckBox(context);
myChkBox.setId(ctrlKey);
myChkBox.setFocusable(false);
myChkBox.setEnabled(false);
if (pageField.ExportValue().contains("Off"))
myChkBox.setChecked(false);
else
myChkBox.setChecked(true);
pageLayout.setLayoutParams(ctrllp);
pageLayout.addView(myChkBox, ctrllp);
}
It works fine even if the first page contains any controls or not. Once I swipe if any other page contains controls then i can see only a blank fragment. If no controls then I can see everything working perfectly. I tried many ways but none worked for me. Any help please.
After some R& D I found the problem in my code. Simply write the code in the main activity instead of Fragment. Render the controls in the activity but not in fragment.
//to display page controls during appload
pageControlsList = pdfRenderer.GetPageControls();
if (pageControlsList != null) {
if (pageControlsList.size() > 0) {
generateControls();
//fillDefaultDocControls();
mfrg.showPage(pdfRenderer.getmCurrentPageNum(), true, this);
}
}
simply generate controls and call the fragment to show the page.

Android: Custom Oscilliscope View Scan Rate

I have written a custom view that shows a simulated oscilloscope, it essentially holds a series of points to plot, and periodically places a pulse (a separate set of points) into the main series. The only problem is that I'd like to update the points at the correct rate of 25mm/sec on one plot and 4mm/sec on another.
setRate() is called before the view is drawn. updateData() is called from a thread that runs a loop at 60fps with SystemClock.elapsedRealtime(). The problem is that pxToDraw is not the right value to keep it at the rate I'd like.
Here is my code
static class FakePlot extends OscView.Plot {
public void setRate(int width, DisplayMetrics dm) {
float xdpi = dm.xdpi;
mWidthMm = width/ xdpi * 25.4f;
pxPerSec =TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, mScanRateMM, dm);
pxPerTick = ((float)width) / mData.length;
lastPulse = SystemClock.elapsedRealtime();
Log.e(TAG,String.format("setRate() %s: pxPerSec=%f pxPerTick=%f",mTitle,pxPerSec,pxPerTick));
}
/**
* Copy the next set of points along
* #param realTime
*/
public void updateData(long realTime) {
long diff = realTime - lastUpdate;
lastUpdate = realTime;
float pxToDraw = ((diff/1000f)/pxPerSec);
float ticksToDraw = pxToDraw/(1f/pxPerTick);
for (int j = 0; j < ticksToDraw; j++){
// update mData with the next ticksToDrawPoints
}
setChanged();
notifyObservers();
}
}
A full working example (without the correct scan rate is here: http://pastebin.com/nHBxumhV)
The correct code is:
float extraToDraw;
public void updateData(long realTime) {
float mmPerTick = mWidthMm/mData.length;
float mmToDraw = (mScanRateMM*diffSec)+extraToDraw;
if(mmToDraw < 1) {extraToDraw = mmToDraw;return;}
extraToDraw = 0;
float drawn = 0;
while(drawn < mmToDraw) {
drawn+= mmPerTick;
// draw the next tick
}
}

How to animate ImageView from center-crop to fill the screen and vice versa (facebook style)?

Background
Facebook app has a nice transition animation between a small image on a post, and an enlarged mode of it that the user can also zoom to it.
As I see it, the animation not only enlarges and moves the imageView according to its previous location and size, but also reveals content instead of stretching the content of the imageView.
This can be seen using the next sketch i've made:
The question
How did they do it? did they really have 2 views animating to reveal the content?
How did they make it so fluid as if it's a single view?
the only tutorial i've seen (link here) of an image that is enlarged to full screen doesn't show well when the thumbnail is set to be center-crop.
Not only that, but it works even on low API of Android.
does anybody know of a library that has a similar ability?
EDIT: I've found a way and posted an answer, but it's based on changing the layoutParams , and i think it's not efficient and recommended.
I've tried using the normal animations and other animation tricks, but for now that's the only thing that worked for me.
If anyone know what to do in order to make it work in a better way, please write it down.
Ok, i've found a possible way to do it.
i've made the layoutParams as variables that keep changing using the ObjectAnimator of the nineOldAndroids library. i think it's not the best way to achieve it since it causes a lot of onDraw and onLayout, but if the container has only a few views and doesn't change its size, maybe it's ok.
the assumption is that the imageView that i animate will take the exact needed size in the end, and that (currently) both the thumbnail and the animated imageView have the same container (but it should be easy to change it.
as i've tested, it is also possible to add zoom features by extending the TouchImageView class . you just set the scale type in the beginning to center-crop, and when the animation ends you set it back to matrix, and if you want, you can set the layoutParams to fill the entire container (and set the margin to 0,0).
i also wonder how come the AnimatorSet didn't work for me, so i will show here something that works, hoping someone could tell me what i should do.
here's the code:
MainActivity.java
public class MainActivity extends Activity {
private static final int IMAGE_RES_ID = R.drawable.test_image_res_id;
private static final int ANIM_DURATION = 5000;
private final Handler mHandler = new Handler();
private ImageView mThumbnailImageView;
private CustomImageView mFullImageView;
private Point mFitSizeBitmap;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFullImageView = (CustomImageView) findViewById(R.id.fullImageView);
mThumbnailImageView = (ImageView) findViewById(R.id.thumbnailImageView);
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
prepareAndStartAnimation();
}
}, 2000);
}
private void prepareAndStartAnimation() {
final int thumbX = mThumbnailImageView.getLeft(), thumbY = mThumbnailImageView.getTop();
final int thumbWidth = mThumbnailImageView.getWidth(), thumbHeight = mThumbnailImageView.getHeight();
final View container = (View) mFullImageView.getParent();
final int containerWidth = container.getWidth(), containerHeight = container.getHeight();
final Options bitmapOptions = getBitmapOptions(getResources(), IMAGE_RES_ID);
mFitSizeBitmap = getFitSize(bitmapOptions.outWidth, bitmapOptions.outHeight, containerWidth, containerHeight);
mThumbnailImageView.setVisibility(View.GONE);
mFullImageView.setVisibility(View.VISIBLE);
mFullImageView.setContentWidth(thumbWidth);
mFullImageView.setContentHeight(thumbHeight);
mFullImageView.setContentX(thumbX);
mFullImageView.setContentY(thumbY);
runEnterAnimation(containerWidth, containerHeight);
}
private Point getFitSize(final int width, final int height, final int containerWidth, final int containerHeight) {
int resultHeight, resultWidth;
resultHeight = height * containerWidth / width;
if (resultHeight <= containerHeight) {
resultWidth = containerWidth;
} else {
resultWidth = width * containerHeight / height;
resultHeight = containerHeight;
}
return new Point(resultWidth, resultHeight);
}
public void runEnterAnimation(final int containerWidth, final int containerHeight) {
final ObjectAnimator widthAnim = ObjectAnimator.ofInt(mFullImageView, "contentWidth", mFitSizeBitmap.x)
.setDuration(ANIM_DURATION);
final ObjectAnimator heightAnim = ObjectAnimator.ofInt(mFullImageView, "contentHeight", mFitSizeBitmap.y)
.setDuration(ANIM_DURATION);
final ObjectAnimator xAnim = ObjectAnimator.ofInt(mFullImageView, "contentX",
(containerWidth - mFitSizeBitmap.x) / 2).setDuration(ANIM_DURATION);
final ObjectAnimator yAnim = ObjectAnimator.ofInt(mFullImageView, "contentY",
(containerHeight - mFitSizeBitmap.y) / 2).setDuration(ANIM_DURATION);
widthAnim.start();
heightAnim.start();
xAnim.start();
yAnim.start();
// TODO check why using AnimatorSet doesn't work here:
// final com.nineoldandroids.animation.AnimatorSet set = new AnimatorSet();
// set.playTogether(widthAnim, heightAnim, xAnim, yAnim);
}
public static BitmapFactory.Options getBitmapOptions(final Resources res, final int resId) {
final BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, bitmapOptions);
return bitmapOptions;
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.facebookstylepictureanimationtest.CustomImageView
android:id="#+id/fullImageView"
android:layout_width="0px"
android:layout_height="0px"
android:background="#33ff0000"
android:scaleType="centerCrop"
android:src="#drawable/test_image_res_id"
android:visibility="invisible" />
<ImageView
android:id="#+id/thumbnailImageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:scaleType="centerCrop"
android:src="#drawable/test_image_res_id" />
</RelativeLayout>
CustomImageView.java
public class CustomImageView extends ImageView {
public CustomImageView(final Context context) {
super(context);
}
public CustomImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public CustomImageView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
public void setContentHeight(final int contentHeight) {
final LayoutParams layoutParams = getLayoutParams();
layoutParams.height = contentHeight;
setLayoutParams(layoutParams);
}
public void setContentWidth(final int contentWidth) {
final LayoutParams layoutParams = getLayoutParams();
layoutParams.width = contentWidth;
setLayoutParams(layoutParams);
}
public int getContentHeight() {
return getLayoutParams().height;
}
public int getContentWidth() {
return getLayoutParams().width;
}
public int getContentX() {
return ((MarginLayoutParams) getLayoutParams()).leftMargin;
}
public void setContentX(final int contentX) {
final MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = contentX;
setLayoutParams(layoutParams);
}
public int getContentY() {
return ((MarginLayoutParams) getLayoutParams()).topMargin;
}
public void setContentY(final int contentY) {
final MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
layoutParams.topMargin = contentY;
setLayoutParams(layoutParams);
}
}
Another solution, if you just want to make an animation of an image from small to large, you can try ActivityOptions.makeThumbnailScaleUpAnimation or makeScaleUpAnimationand see if they suit you.
http://developer.android.com/reference/android/app/ActivityOptions.html#makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int)
You can achieve this through Transition Api, and this the resut gif:
essential code below:
private void zoomIn() {
ViewGroup.LayoutParams layoutParams = mImage.getLayoutParams();
int width = layoutParams.width;
int height = layoutParams.height;
layoutParams.width = (int) (width * 2);
layoutParams.height = height * 2;
mImage.setLayoutParams(layoutParams);
mImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
TransitionSet transitionSet = new TransitionSet();
Transition bound = new ChangeBounds();
transitionSet.addTransition(bound);
Transition changeImageTransform = new ChangeImageTransform();
transitionSet.addTransition(changeImageTransform);
transitionSet.setDuration(1000);
TransitionManager.beginDelayedTransition(mRootView, transitionSet);
}
View demo on github
sdk version >= 21
I found a way to get a similar affect in a quick prototype. It might not be suitable for production use (I'm still investigating), but it is quick and easy.
Use a fade transition on your activity/fragment transition (which starts with the ImageView in exactly the same position). The fragment version:
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
...etc
The activity version:
Intent intent = new Intent(context, MyDetailActivity.class);
startActivity(intent);
getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
This gives a smooth transition without a flicker.
Adjust the layouts dynamically in the onStart() of the new fragment (you need to save member fields to the appropriate parts of your UI in onCreateView, and add some flags to ensure this code only gets called once).
#Override
public void onStart() {
super.onStart();
// Remove the padding on any layouts that the image view is inside
mMainLayout.setPadding(0, 0, 0, 0);
// Get the screen size using a utility method, e.g.
// http://stackoverflow.com/a/12082061/112705
// then work out your desired height, e.g. using the image aspect ratio.
int desiredHeight = (int) (screenWidth * imgAspectRatio);
// Resize the image to fill the whole screen width, removing
// any layout margins that it might have (you may need to remove
// padding too)
LinearLayout.LayoutParams layoutParams =
new LinearLayout.LayoutParams(screenWidth, desiredHeight);
layoutParams.setMargins(0, 0, 0, 0);
mImageView.setLayoutParams(layoutParams);
}
I think the easiest way is to animate the height of the ImageView (a regular imageview, not necessary a custom view) while keeping the scaleType to centerCrop until full height, which you can know in advance if you set the image height to wrap_content in your layout and then use a ViewTreeObserver to know when the layout has ended, so you can get the ImageView height and then set the new "collapsed" height. I have not tested it but this is how I would do it.
You can also have a look at this post, they do something similar http://nerds.airbnb.com/host-experience-android/
I'm not sure why everyone is talking about the framework. Using other peoples code can be great at times; but it sounds like what you are after is precise control over the look. By getting access to the graphics context you can have that. The task is pretty simple in any environment that has a graphics context. In android you can get it by overriding the onDraw method and using the Canvas Object. It has everything you need to draw an image at many different scales, positions and clippings. You can even use a matrix if your familiar with that type of thing.
Steps
Make sure you have exact control of positioning, scale, and clip. This means disabling any layouts or auto-alignment that might be setup inside your objects container.
Figure you out what your parameter t will be for linear interpolation and how you will want it to relate to time. How fast or slow, and will there be any easing. t should be dependent on time.
After the thumbnails are cached, load the full scale image in the background. But don't show it yet.
When the animation trigger fires, show the large image and drive your animation with your t parameter using interpolation between the initial properties' states to the final properties' states. Do this for all three properties, position, scale and clip. So for all properties do the following:
Sinterpolated = Sinitial * (t-1) + Sfinal * t;
// where
// t is between 0.0 and 1.0
// and S is the states value
// for every part of scale, position, and clip
//
// Sinitial is what you are going from
// Sfinal is what you are going to
//
// t should change from 0.0->1.0 in
// over time anywhere from 12/sec or 60/sec.
If all your properties are driven by the same parameter the animation will be smooth. As an added bonus, here is a tip for timing. As long as you can keep your t parameter between 0 and 1, easing in or out can be hacked with one line of code:
// After your t is all setup
t = t * t; // for easing in
// or
t = Math.sqrt(t); // for easing out
I made a sample code in Github.
The key of this code is using canvas.clipRect().
But, it only works when the CroppedImageview is match_parent.
To explain simply,
I leave scale and translation animation to ViewPropertyAnimator.
Then, I can focus on cropping the image.
Like above picture, calculate the clipping region, and change the clip region to final view size.
AnimationController
class ZoomAnimationController(private val view: CroppedImageView, startRect: Rect, private val viewRect: Rect, imageSize: Size) {
companion object {
const val DURATION = 300L
}
private val startViewRect: RectF
private val scale: Float
private val startClipRect: RectF
private val animatingRect: Rect
private var cropAnimation: ValueAnimator? = null
init {
val startImageRect = getProportionalRect(startRect, imageSize, ImageView.ScaleType.CENTER_CROP)
startViewRect = getProportionalRect(startImageRect, viewRect.getSize(), ImageView.ScaleType.CENTER_CROP)
scale = startViewRect.width() / viewRect.width()
val finalImageRect = getProportionalRect(viewRect, imageSize, ImageView.ScaleType.FIT_CENTER)
startClipRect = getProportionalRect(finalImageRect, startRect.getSize() / scale, ImageView.ScaleType.FIT_CENTER)
animatingRect = Rect()
startClipRect.round(animatingRect)
}
fun init() {
view.x = startViewRect.left
view.y = startViewRect.top
view.pivotX = 0f
view.pivotY = 0f
view.scaleX = scale
view.scaleY = scale
view.setClipRegion(animatingRect)
}
fun startAnimation() {
cropAnimation = createCropAnimator().apply {
start()
}
view.animate()
.x(0f)
.y(0f)
.scaleX(1f)
.scaleY(1f)
.setDuration(DURATION)
.start()
}
private fun createCropAnimator(): ValueAnimator {
return ValueAnimator.ofFloat(0f, 1f).apply {
duration = DURATION
addUpdateListener {
val weight = animatedValue as Float
animatingRect.set(
(startClipRect.left * (1 - weight) + viewRect.left * weight).toInt(),
(startClipRect.top * (1 - weight) + viewRect.top * weight).toInt(),
(startClipRect.right * (1 - weight) + viewRect.right * weight).toInt(),
(startClipRect.bottom * (1 - weight) + viewRect.bottom * weight).toInt()
)
Log.d("SSO", "animatingRect=$animatingRect")
view.setClipRegion(animatingRect)
}
}
}
private fun getProportionalRect(viewRect: Rect, imageSize: Size, scaleType: ImageView.ScaleType): RectF {
return getProportionalRect(RectF(viewRect), imageSize, scaleType)
}
private fun getProportionalRect(viewRect: RectF, imageSize: Size, scaleType: ImageView.ScaleType): RectF {
val viewRatio = viewRect.height() / viewRect.width()
if ((scaleType == ImageView.ScaleType.FIT_CENTER && viewRatio > imageSize.ratio)
|| (scaleType == ImageView.ScaleType.CENTER_CROP && viewRatio <= imageSize.ratio)) {
val width = viewRect.width()
val height = width * imageSize.ratio
val paddingY = (height - viewRect.height()) / 2f
return RectF(viewRect.left, viewRect.top - paddingY, viewRect.right, viewRect.bottom + paddingY)
} else if ((scaleType == ImageView.ScaleType.FIT_CENTER && viewRatio <= imageSize.ratio)
|| (scaleType == ImageView.ScaleType.CENTER_CROP && viewRatio > imageSize.ratio)){
val height = viewRect.height()
val width = height / imageSize.ratio
val paddingX = (width - viewRect.width()) / 2f
return RectF(viewRect.left - paddingX, viewRect.top, viewRect.right + paddingX, viewRect.bottom)
}
return RectF()
}
CroppedImageView
override fun onDraw(canvas: Canvas?) {
if (clipRect.width() > 0 && clipRect.height() > 0) {
canvas?.clipRect(clipRect)
}
super.onDraw(canvas)
}
fun setClipRegion(rect: Rect) {
clipRect.set(rect)
invalidate()
}
it only works when the CroppedImageview is match_parent,
because
The paths from start to end is included in CroppedImageView. If not, animation is not shown. So, Making it's size match_parent is easy to think.
I didn't implement the code for special case...

libgdx particleEffect rotation

I draw fire on my android device with libgdx:
ParticleEffect effect;
ParticleEffectPool fireEffectPool;
Array<PooledEffect> effects = new Array<PooledEffect>();
#Override
public void create()
{
...
effect = new ParticleEffect();
effect.load(Gdx.files.internal("particles/fire01.p"), Gdx.files.internal("image"));
effect.setFlip(true, false);
fireEffectPool = new ParticleEffectPool(effect, 1000, 3000);
PooledEffect myEffect = fireEffectPool.obtain();
myEffect.setPosition(200, 400);
effects.add(myEffect);
...
}
Can I rotate, set speed or scale my effect programmatically?
I found the solution to the particle effect rotation problem by using this code as base
http://badlogicgames.com/forum/viewtopic.php?f=11&t=7060#p32607
And adding a small change to conserve the amplitude of the effect. Hope it helps.
public void rotateBy(float amountInDegrees) {
Array<ParticleEmitter> emitters = particleEffect.getEmitters();
for (int i = 0; i < emitters.size; i++) {
ScaledNumericValue val = emitters.get(i).getAngle();
float amplitude = (val.getHighMax() - val.getHighMin()) / 2f;
float h1 = amountInDegrees + amplitude;
float h2 = amountInDegrees - amplitude;
val.setHigh(h1, h2);
val.setLow(amountInDegrees);
}
}
}
Yes. Check out the ParticleEmitterTest: https://github.com/libgdx/libgdx/blob/master/tests/gdx-tests/src/com/badlogic/gdx/tests/ParticleEmitterTest.java
You just need to obtain a ParticleEmitter:
emitter = effect.getEmitters().first();
emitter.getScale().setHigh(5, 20);

Categories

Resources