I wanna make a reaction button like Facebook, so something like this
Now i manage to make something similar with a DialogFragment but I am having some trouble with the position of my dialog, this code sometimes works but some other time the dialog is placed in the wrong place. Can someone give me some advice or suggest me a better way to do this?
My Dialog Class
public class ReactionsFragment extends DialogFragment {
public static final String TAG = "ReactionsFragment";
private View paretnView;
public ReactionsFragment(View view, String reviewId,Reactionable reactedContent) {
this.paretnView = view;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_reactions, container, false);
initComponent(view);
setDialogPosition();
return view;
}
public void initComponent(View rootView) {...}
public void setDialogPosition() {
if (paretnView == null) {
return; // Leave the dialog in default position
}
int[] location = new int[2];
paretnView.getLocationOnScreen(location);
int sourceX = location[0];
int sourceY = location[1];
Window window = getDialog().getWindow();
window.setGravity(Gravity.TOP|Gravity.LEFT);
WindowManager.LayoutParams params = window.getAttributes();
params.x = sourceX - dpToPx(0);
params.y = sourceY - dpToPx(110);
window.setAttributes(params);
}
public int dpToPx(float valueInDp) {
DisplayMetrics metrics = getActivity().getResources().getDisplayMetrics();
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}
}
Use PopupWindow. I think this is exactly what you are looking for:
https://developer.android.com/reference/android/widget/PopupWindow
You can set the exact position for the popup to appear using this method
Related
I have encountered a weird bug in my fragment class: imageViews "disappear" if I rotate/reload the view. From my understanding, rotating/reloading destroys the View and re-creates it, so local variables and local view elements may not preserve. However, I have made a manual button that should manually render the images again after I click it, yet the ImageViews stay gone even if I manually reset their imageResource or imageBackground resource. Note that these imageviews are animated-drawables. Below is most of my code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
mView = rootView; //mView is global
return rootView;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d("gnereed id", "id is "+R.id.generate_button);
final Button generate_button = getView().findViewById(R.id.generate_button);
if ( bottleMap == null ) bottleMap = new Hashtable();
// the code sets all images to invisible onCreate
// their visibility will be changed when a bottle is "created"
ImageView[] bottles = new ImageView[7];
for (int i = 0; i < bottleAry.length; i++){
bottles[i] = getView().findViewById(bottleAry[i]);
bottles[i].setVisibility(View.GONE);
}
// this is a button that generates a new bottle and manually refreshes all previous bottle
// If fragment has not been reloaded/rotated then everything works here
// after fragment reloads, new bottles can be generated but old bottles do not re-render.
generate_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Bottle bottle = new Bottle("123", bottleList.size());
bottle.setVisible();
bottleList.add(bottle);
Log.e(" mView : ", mView.toString());
// for all bottles created, re-render them
for (int i = 0; i < bottleList.size(); i ++) {
bottleList.get(i).reRender();
}
}
});
}
public class Bottle{
String message;
ImageView bottleLocation;
int imageSrc;
int avail_index;
int bottle_index;
int locationID;
AnimationDrawable bottleAnimation;
public Bottle(String msg, int bottle_index){
message = msg;
this.bottle_index = bottle_index;
locationID = getRandomBottleLocation();
bottleLocation = getView().findViewById(locationID);
Log.e(" old View : ", getView().toString());
// sets the image source and sets visible, lastly start animation
imageSrc = getRandomBottleImg();
bottleLocation.setBackgroundResource(imageSrc);
bottleMap.put(Integer.toString(locationID), imageSrc);
bottleAnimation = (AnimationDrawable) bottleLocation.getBackground();
bottleAnimation.start();
bottleLocation.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), ViewBottleActivity.class));
availableLocation[avail_index] = false;
bottleMap.remove(Integer.toString(locationID));
bottleAnimation.stop();
bottleLocation.setVisibility(View.GONE);
bottleList.remove(bottle_index);
}
});
}
public int getRandomBottleImg(){
int bottle;
Random rand = new Random();
bottle = imgAry[rand.nextInt(imgAry.length)];
return bottle;
}
public int getRandomBottleLocation(){
int location;
Random rand = new Random();
avail_index = rand.nextInt(bottleAry.length);
while (availableLocation[avail_index]){
avail_index = rand.nextInt(bottleAry.length);
}
location = bottleAry[avail_index];
availableLocation[avail_index] = true;
return location;
}
public void reRender(){
Log.e("location ID is:" , Integer.toString(this.locationID));
bottleLocation = mView.findViewById(this.locationID);
Log.e("ImageView is:" , bottleLocation.toString());
imageSrc = getRandomBottleImg();
bottleLocation.setBackgroundResource(imageSrc);
bottleAnimation = (AnimationDrawable) bottleLocation.getBackground();
bottleAnimation.stop();
bottleAnimation.start();
this.setVisible();
}
public void setVisible(){
bottleLocation.setVisibility(View.VISIBLE);
}
I fixed the problem. What I learned is that you cannot use
View.findViewByID(viewID)
outside of onViewCreated(). Notice how I implemented reRender() function inside the onViewCreated() but it didn't work? The result is that mView.findViewByID(viewID) is actually executed OUTSIDE OF onViewCreated() although I call the function from WITHIN.
Yes, the line will be executed, but upon inspection, mView.findViewByID(viewID) will return TWO DIFFERENT objects when called from inside onViewCreated() and when called from a function that is called from onViewCreated().
This is very counterintuitive, especially for us who's taught to deploy the DRY(do not repeat urself) principle. The fix is just to simply not write outside functions for handling View.
I have fragment from which I'm launching activity with shared element transition that has viewpager in it, the enter transition works fine but when i scroll in view pager and finish transition the shared image comes from left side which is not desired it should reposition itself to where it was launched, here is my code:
Intent myIntent = new Intent(getActivity(), EnlargeActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(),
imageView,
ViewCompat.getTransitionName(imageView));
startActivity(myIntent, options.toBundle());
I'm updating view and its name in activity that contains viewpager when finishing activity, but its going with blink:
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
ViewGroup viewGroup = (ViewGroup) viewPagerDetail.getAdapter()
.instantiateItem(viewPagerDetail, viewPagerDetail.getCurrentItem());
if (viewGroup == null) {
return;
}
// Map the first shared element name to the child ImageView.
sharedElements.put(viewGroup.findViewById(R.id.img).getTransitionName(), viewGroup.findViewById(R.id.img));
// setExitSharedElementCallback((SharedElementCallback) this);
}
});
super.finishAfterTransition();
Basically, Android start the transition with your pre-defined View and transitionName and automatically use the same properties for the return transition. When you change your focused View in ViewPager, Android doesn't know about that and keep the transition on the previous one on its way back. So you need to inform Android about the changes:
Remap the transition properties: Use setEnterSharedElementCallback to change the transitionName and View to the new one before returning from Activity2.
Wait for the Activity1 to finish rendering addOnPreDrawListener.
It's a bit complex in the final implementation. But you can look at my sample code https://github.com/tamhuynhit/PhotoGallery. I try to implement the shared-element-transition from many simple to complex sections.
Your problem appeared from Level 3 and solved in Level 4.
I am writing a tutorial about this but it's not in English so hope the code can help
UPDATE 1: Work flow
Here is how I implement it in my code:
Override finishAfterTransition in Activity2 and call setEnterSharedElementCallback method to re-map the current selected item in ViewPager. Also, call setResult to pass the new selected index back to previous activity here.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View selectedView = getSelectedView();
if (selectedView == null)
return;
// Clear all current shared views and names
names.clear();
sharedElements.clear();
// Store new selected view and name
String transitionName = ViewCompat.getTransitionName(selectedView);
names.add(transitionName);
sharedElements.put(transitionName, selectedView);
setExitSharedElementCallback((SharedElementCallback) null);
}
});
Intent intent = new Intent();
intent.putExtra(PHOTO_FOCUSED_INDEX, mCurrentIndex);
setResult(RESULT_PHOTO_CLOSED, intent);
super.finishAfterTransition();
}
Write a custom ShareElementCallback so I can set the callback before knowing which View is going to be used.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static class CustomSharedElementCallback extends SharedElementCallback {
private View mView;
/**
* Set the transtion View to the callback, this should be called before starting the transition so the View is not null
*/
public void setView(View view) {
mView = view;
}
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
// Store new selected view and name
String transitionName = ViewCompat.getTransitionName(mView);
names.add(transitionName);
sharedElements.put(transitionName, mView);
}
}
Override onActivityReenter in Activity1, get the selected index from the result Intent. Set setExitSharedElementCallback to re-map new selected View when the transition begins.Call supportPostponeEnterTransition to delay a bit because your new View may not be rendered at this point. Use getViewTreeObserver().addOnPreDrawListener to listen for the layout changes, find the right View by the selected index and continue the transition supportStartPostponedEnterTransition.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onActivityReenter(int resultCode, Intent data) {
if (resultCode != LevelFourFullPhotoActivity.RESULT_PHOTO_CLOSED || data == null)
return;
final int selectedIndex = data.getIntExtra(LevelFourFullPhotoActivity.PHOTO_FOCUSED_INDEX, -1);
if (selectedIndex == -1)
return;
// Scroll to the new selected view in case it's not currently visible on the screen
mPhotoList.scrollToPosition(selectedIndex);
final CustomSharedElementCallback callback = new CustomSharedElementCallback();
getActivity().setExitSharedElementCallback(callback);
// Listen for the transition end and clear all registered callback
getActivity().getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(Transition transition) {}
#Override
public void onTransitionPause(Transition transition) {}
#Override
public void onTransitionResume(Transition transition) {}
#Override
public void onTransitionEnd(Transition transition) {
removeCallback();
}
#Override
public void onTransitionCancel(Transition transition) {
removeCallback();
}
private void removeCallback() {
if (getActivity() != null) {
getActivity().getWindow().getSharedElementExitTransition().removeListener(this);
getActivity().setExitSharedElementCallback((SharedElementCallback) null);
}
}
});
// Pause transition until the selected view is fully drawn
getActivity().supportPostponeEnterTransition();
// Listen for the RecyclerView pre draw to make sure the selected view is visible,
// and findViewHolderForAdapterPosition will return a non null ViewHolder
mPhotoList.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mPhotoList.getViewTreeObserver().removeOnPreDrawListener(this);
RecyclerView.ViewHolder holder = mPhotoList.findViewHolderForAdapterPosition(selectedIndex);
if (holder instanceof ViewHolder) {
callback.setView(((ViewHolder) holder).mPhotoImg);
}
// Continue the transition
getActivity().supportStartPostponedEnterTransition();
return true;
}
});
}
UPDATE 2: getSelectedItem
To get selected View from the ViewPager, don't use getChildAt or you get the wrong View, use findViewWithTag instead
In the PagerAdapter.instantiateItem, use position as tag for each View:
#Override
public View instantiateItem(ViewGroup container, int position) {
// Create the View
view.setTag(position)
// ...
}
Listen to onPageSelected event to get the selected index:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
mSelectedIndex = position;
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Call getSelectedView to get the current view by the selected index
private View getSelectedView() {
try {
return mPhotoViewPager.findViewWithTag(mSelectedIndex);
} catch (IndexOutOfBoundsException | NullPointerException ex) {
return null;
}
}
This is actually a default behavior, I was struggling SharedElementTransitions a lot, but I have nested fragments. I got my solution from an article (very recent article), it shows an implementation with a RecyclerView, which I assume you have. In short, the solution is to override onLayoutChange :
recyclerView.addOnLayoutChangeListener(
new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View view,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
recyclerView.removeOnLayoutChangeListener(this);
final RecyclerView.LayoutManager layoutManager =
recyclerView.getLayoutManager();
View viewAtPosition =
layoutManager.findViewByPosition(MainActivity.currentPosition);
// Scroll to position if the view for the current position is null (not
// currently part of layout manager children), or it's not completely
// visible.
if (viewAtPosition == null
|| layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){
recyclerView.post(()
-> layoutManager.scrollToPosition(MainActivity.currentPosition));
}
}
});
Here is the article, and you will also find the project on GitHub.
I have a RecyclerView with Expandable Child Views, when the child ViewGroup is clicked it inflates an amount of views animating the ViewGroup height from 0 to the measured viewgroup height, like the following gif:
The problem is: I'm calling smoothScrollToPosition on recyclerView, it smooth scroll to the view position, but it considers the current view height, which is still not expanded, in the above gif i'm touching on the under view of the recyclerview, which dont scroll to position because the view is already visible, but when i touch again (calling the smoothscrolltoposition again) it scroll the view to the correct position, because the view is already expanded.
Is there any approach to scroll the view to the top of screen or just scroll to make content visible?
For references:
This is the method called to inflate the views:
collapsible_content.removeAllViews();
for(int i = 0; i < 5; i++) {
View link_view = getLayoutInflater().inflate(R.layout.list_item_timeline_step_link, collapsible_content, false);
TextView text = (TextView) link_view.findViewById(R.id.step_link_text);
text.setText("Test");
collapsible_content.addView(link_view);
}
And this is my method to expand:
public void toggle() {
collapsible_content.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
Animation a;
if (mExpanded) {
a = new ExpandAnimation(collapsible_content.getLayoutParams().height, 0);
} else {
a = new ExpandAnimation(collapsible_content.getLayoutParams().height, getMeasuredHeight());
}
a.setDuration(mAnimationDuration);
collapsible_content.startAnimation(a);
mExpanded = !mExpanded;
}
And the animation:
private class ExpandAnimation extends Animation {
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startHeight, int endHeight) {
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
final int newHeight = (int) (mStartHeight + mDeltaHeight *
interpolatedTime);
collapsible_content getLayoutParams().height = newHeight;
if (newHeight <= 0) {
collapsible_content setVisibility(View.GONE);
} else {
collapsible_content setVisibility(View.VISIBLE);
}
collapsible_content requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
My solution was to constant check for view bottom within applyTransformation method, and compare it with RecyclerView height, if the bottom get higher than the RV height, i scroll by the diff values:
final int bottom = collapsible_content.getBottom();
final int listViewHeight = mRecyclerView.getHeight();
if (bottom > listViewHeight) {
final int top = collapsible_content.getTop();
if (top > 0) {
mRecyclerView.smoothScrollBy(0, Math.min(bottom - listViewHeight + mRecyclerView.getPaddingBottom(), top));
}
}
The trick was to use Math.min to get the view top, so it don't scroll up making the top not visible.
Solution based on ListViewAnimations
Add an animationlistener and start the scrolling of the recyclerview after the expanding animation is finished.
I have a space ship ImageView that I'm rotating with the help of the following AnimatorSet:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
<objectAnimator
android:duration="5000"
android:propertyName="rotation"
android:repeatCount="infinite"
android:repeatMode="restart"
android:valueTo="-360"
android:valueType="floatType"
android:interpolator="#android:anim/linear_interpolator" />
In order to start and end the animation I'm using this OnTouchListener:
turnShipLeft_btn.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
spaceShip_setLeft.start();
} else if(arg1.getAction() == MotionEvent.ACTION_UP){
spaceShip_setLeft.cancel();
}
return true;
}
});
Once spaceShip_setLeft.cancel(); is executed, I want to do 2 things:
Store the exact rotation degree where it was stopped in a local variable.
Set my rotation degree variable on the next AnimatorSet's android:valueFrom="" so it will start the animation from the exact position where spaceShip_setLeft animation ended.
I'm breaking my head over this for too long now. Any help will be very much appreciated!
PS. If you can include you'r own code snippets, it would be totally awesome!
Don't use XML if you want to use dynamic values.
For simplicity, I wrote my own little demo inside of the default Android Application Project (the newest one that has the fragment). You should be able to adapt to your code as necessary.
public static class PlaceholderFragment extends Fragment {
Button btn_A;
Button btn_B;
TextView hello;
ObjectAnimator mAnimation;
AnimatorSet transSet;
Float valuefrom=0f;
Float valueto=-360f;
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
btn_A= (Button) rootView.findViewById(R.id.button1);
btn_B= (Button) rootView.findViewById(R.id.button2);
hello= (TextView) rootView.findViewById(R.id.textView1);
btn_A.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
if(!transSet.isRunning()){
transSet.start();
}
}
});
btn_B.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
Float save = (Float) mAnimation.getAnimatedValue();
Log.d("TAG", "Value" +save);
valuefrom= save;
valueto= save-360;
transSet.cancel();
//re-init the animation
doObjectAnimator();
}
});
//init the animation
doObjectAnimator();
return rootView;
}
public void doObjectAnimator(){
mAnimation= ObjectAnimator.ofFloat(hello, "rotation", valuefrom, valueto);
mAnimation.setInterpolator(new LinearInterpolator());
mAnimation.setDuration(5000);
mAnimation.setRepeatCount(Animation.INFINITE);
mAnimation.setRepeatMode(Animation.RESTART);
//define your other animations here and add them to the set like: set.play(anim1).before(anim2);
transSet= new AnimatorSet();
transSet.play(mAnimation);
}
}
I am trying to trigger a click on the Carousel. I want that if I press the forward button, it automatically triggers the click on the carousel and then move forward. The manual click (physical touch) is working but performClick() is not. The code is as follows
//************* Forward Button: Select Objects *************
Button forwardButton = (Button)this.findViewById(R.id.ForwardButton);
forwardButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(NewFieldTrip.this, SelectObjects.class);
//ImagePosition = (int)carousel.getSelectedItemId();
carousel.performClick();
i.putExtra("SelectedScene",ImagePosition);
startActivity(i);
}
});
carousel.setOnItemClickListener(new CarouselAdapter.OnItemClickListener(){
#Override
public void onItemClick(CarouselAdapter<?> parent, View view,
int position, long id) {
Toast.makeText(NewFieldTrip.this, "Select Position=" + position, Toast.LENGTH_SHORT).show();
ImagePosition = position;
}
});
A helping hand would be great :)
EDIT:
public void scrollToChild(int i){
CarouselImageView view = (CarouselImageView)getAdapter().getView(i, null, null);
float angle = view.getCurrentAngle();
if(angle == 0)
return;
if(angle > 180.0f)
angle = 360.0f - angle;
else
angle = -angle;
mFlingRunnable.startUsingDistance(-angle);
}
What type is carousel of? ListView?
So if carousel is ListView then what event are you expected on ListView clicking?
performClick() trigger OnClickListener, which you didn't set to cаrousel. You set OnItemClickListener, so you have to call performItemClick(...). Try it.
UPD:
Try to do folowing:
1) make method Carousel.scrollToChild(int i) public
2)
int itemCount = carousel.getAdapter().getCount();
int item = new Random().nextInt(itemCount);
View view - carousel.getAdapter().getView(item, null, null);
int itemId = carousel.getAdapter().getItemId(item);
carousel.scrollToChild(item);
carousel.performItemClick(view, item, itemId);