Can you create different actions for clicking on the same ImageView but different parts? Say I have an ImageView and I want for it to act differently if I click on the top of the ImageView and differently if I click on the middle part of the ImageView . Can I achieve this in android
The best way is to add an OnTouchListener to the view and handle click event by yourself related to the touch coordinate.
But if you need an easy way which I don't suggest because it can lead to performance issue you can add transparent views on top of your ImageView and add different OnClickListener for them.
if you have fixed sections in your imageview you can use a frame layout and add 3(for example) transparent views on your imageview and then set click listeners on those views.
but if you have dynamic sections you can handle it by combining onTouchListener and onClickListener, for example :
// just store the touch X,Y coordinates
View.OnTouchListener touchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// save the X,Y coordinates
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
lastTouchDownXY[0] = event.getX();
lastTouchDownXY[1] = event.getY();
}
// let the touch event pass on to whoever needs it
return false;
}
};
View.OnClickListener clickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
// retrieve the stored coordinates
float x = lastTouchDownXY[0];
float y = lastTouchDownXY[1];
// use the coordinates for whatever or check the clickable areas
Log.i("TAG", "onLongClick: x = " + x + ", y = " + y);
}
};
I think the best way for this is that you declare a frameLayout as parent view then you can declare views on your imageView then clicking each view will perform what you want.
Related
I have the following component structure:
- CustomComponent extends ConstraintLayout
- ImageView
- ImageView
- ViewPager2
- CustomFragment extends ListFragment
- ListView
- ListItem extends ConstraintLayout
- ImageView
- ListItem extends ConstraintLayout
- ImageView
- ListItem extends ConstraintLayout
- ImageView
- etc...
Visually it looks like this (I left out ListView for brevity):
The ImageView in ListItem will act as a drag button for dragging the ListItem, so that the items can be reordered. But ultimately, I also want to be able to drag a ListItem outside the ViewPager2 and drop it on one of the ImageViews in the CustomComponent. I think I will implements this by temporarily hiding the original and then "clone" and attach the ListItem as a child to CustomComponent.
However, before even getting there, the problem currently is that MotionEvent.ACTION_MOVE on the ImageView is constrained to its parent ListItem as illustrated by the red arrows. As soon as the touch gesture goes outside ListItem, I stop receiving MotionEvent.ACTION_MOVE events.
This is the listener I currently have on the drag button:
dragButton.setOnTouchListener((view, event) -> {
System.out.println("ListItem to drag is: " + view.getParent());
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dragging = false;
break;
case MotionEvent.ACTION_MOVE:
if(!dragging) {
dragging = true;
System.out.println('drag start');
}
else {
System.out.println('drag');
}
break;
case MotionEvent.ACTION_UP:
if(dragging) {
dragging = false;
System.out.println('drag end');
}
break;
}
return true;
});
While I imagine that it will keep being detected for a bit if I were to move the ListItem along with the MotionEvent coordinates, I doubt it would be reliable, as it might lag behind and thereby slip from under the touch gesture. Moreover: with the above mentioned "cloning", this is not even relevant anymore, since its the "clone" that will be moving — not the original.
So, is there a way to detect MotionEvent.ACTION_MOVE outside ListItem when dragging the ImageView, as illustrated by the blue arrows?
I know about onInterceptTouchEvent(), which I could override in CustomComponent, but that gives me no information about which View the event is meant for, whereas in the above OnTouchListener I can easily determine which ListItem to drag by view.getParent().
I'm open to hearing completely other ideas of how to approach this as well, of course.
I have an ImageView inside of a view pager with an ActionBar at the top. I would like to be able to single tap to hide the action bar, and I would also like to be able to pinch zoom and pan on each ImageView.
To implement the single tap to hide the action bar I have a simple OnClickListener that hides it.
To implement the pinch zoom and pan on each ImageView I am using the PhotoView Library Project.
I am having issues because only one touch event listener can be associated with an ImageView, and the implementing the PhotoView Library project overwrites my OnClickListener to hide the ActionBar with,
parent.requestDisallowInterceptTouchEvent(true);
I am not sure how to go about getting both implemented at the same time. It seems like the only solution is to create my own Pinch Zoom ImageView in order to control touch events myself.
Found out that the PhotoView library actually allows me to set onViewTap for the PhotoViewAttacher object which is exactly what I wanted.
To create the PhotoViewAttacher in the current Fragment/Activity have it implement PhotoViewAttacher.OnViewTapListener, create the attacher,
PhotoViewAttacher mAttacher = new PhotoViewAttacher(imageView);
mAttacher.setOnViewTapListener(this);
and add the following function,
public void onViewTap(View view, float x, float y) {
// your code here
}
Source
You'll have to override the PhotoView library itself. If you look at the source code, the PhotoViewAttacher class is the one that handles the onTouch events.
You'll have to add the special funcionality you're looking for at this part of the code (specially, the ACTION_DOWN) event:
#Override
public final boolean onTouch(View v, MotionEvent ev) {
boolean handled = false;
if (mZoomEnabled && hasDrawable((ImageView) v)) {
ViewParent parent = v.getParent();
switch (ev.getAction()) {
case ACTION_DOWN:
// First, disable the Parent from intercepting the touch
// event
if (null != parent)
parent.requestDisallowInterceptTouchEvent(true);
else
Log.i(LOG_TAG, "onTouch getParent() returned null");
// If we're flinging, and the user presses down, cancel
// fling
cancelFling();
break;
case ACTION_CANCEL:
case ACTION_UP:
// If the user has zoomed less than min scale, zoom back
// to min scale
if (getScale() < mMinScale) {
RectF rect = getDisplayRect();
if (null != rect) {
v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
rect.centerX(), rect.centerY()));
handled = true;
}
}
break;
}
// Check to see if the user double tapped
if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
handled = true;
}
if (!handled && null != parent) {
parent.requestDisallowInterceptTouchEvent(false);
}
// Finally, try the Scale/Drag detector
if (null != mScaleDragDetector
&& mScaleDragDetector.onTouchEvent(ev)) {
handled = true;
}
}
return handled;
}
I want to make a drop down menu, like a status menu, that is hidden when the activity starts, and when it's pressed or slid it opens like the image below..
http://i.stack.imgur.com/jeq5z.png
My layout currently has a RelativeLayout for the top bar and a ScrollView for the text.. between those, i'd like to put the menu..
I'm not doing this app on phonegap or anything like that, just java and xml..
Thanks in advance
Edit:
Thank you all for your help! I end up doing a FrameLayout that was set off the screen with the translationY and then, when clicked, just slide up and down.. Here's the snipped.. I'll just leave it here in case someone else needs it.
on layout.xml
<FrameLayout
android:id="#+id/layout_FrameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00ffffff" >
<!-- stuf -->
</FrameLayout>
on activity.java
private FrameLayout statusDrawer = null;
private int statusDrawerHeight; // height of the FrameLayout (generated automatically)
private int statusDrawerDragButtonHeight = 30 + 5; //height of the DragButton + height of the border
private boolean statusDrawerOpened = false;
private int statusDrawerDuration = 750; //time in milliseconds
private TimeInterpolator interpolator = null; //type of animation see#developer.android.com/reference/android/animation/TimeInterpolator.html
#Override
protected void onCreated(Bundle savedInstanceState){
statusDrawer = (FrameLayout) findViewById(R.id.layout_FrameLayout);
interpolator = new AccelerateDecelerateInterpolator();
statusDrawer.post(new Runnable() {
#Override
public void run() {
statusDrawerHeight = statusDrawer.getHeight();
statusDrawer.setTranslationY(-statusDrawerHeight+statusDrawerDragButtonHeight);
}
});
statusDrawer.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v){
if(statusDrawerOpened) {
statusDrawer.animate()
.translationY(-statusDrawerHeight+statusDrawerDragButtonHeight)
.setDuration(statusDrawerDuration)
.setInterpolator(interpolator)
.start();
} else {
statusDrawer.animate()
.translationY(0)
.setDuration(statusDrawerDuration)
.setInterpolator(interpolator)
.start();
}
statusDrawerOpened = !statusDrawerOpened;
}
});
}
Use a FrameLayout as the root layout. Add the drop menu layout as in the right side of your picture. Call
menuView.setTranslationY(-view.getHeight);
on this view to initially hide the drop down menu when the activity is started. Make sure menuView only refers to the drop down view part without the small tab button. When the user touches the tab animate translationY to 0 so that the layout will slide down
ObjectAnimator.ofFloat(dropDownView, "translationY", -view.getHeight, 0).setDuration(200).start();
whereby dropDownView refers to the complete drop down menu.
Using ObjectAnimator requires API level 11. If you need to support older API levels, use http://developer.android.com/reference/android/view/animation/TranslateAnimation.html (which has some down sides).
If you instead want add a sliding effect, e.g. the sliding menu is moving with together with the finger, install a OnTouchListener:
dropDownTab.setOnTouchListener(new OnTouchListener() {
public void onTouch(View v, MotionEvent e) {
// Make the drop down menu finger follow the finger position.
// Use again dropDownView.setTranslationY(...) to move the view.
// If the drop down menu has been dragged a certain distance, make it move out by itself using the animation as above.
}
});
I'm trying to do an animation that goes like this:
The context:
I have two EditText's I need that when you click over one, the another one come out from behind the first one. Here you have some pictures to get a image of what I want.
To do this obviously I need a TranslateAnimation in order to move the second EditText from behind the first one.
My approach:
The first thing that I could thought was in use a FrameLayout put the EditText one over another, and then in the onTouch event of the first one do a TranslateAnimation on the second one. The problem with this is that if I have the FrameLayout height in wrap_content then the animation will be invisible to the user. And if I change on runtime the height of the FrameLayout I will leave a void below the first EditText as you can see in this picture:
The second solution that I thought was add one AnimationListener to the TranslateAnimation and in the onAnimationStart method change the height of the FrameLayout. But the problem with this is that the height of the FrameLayout changes too abruptly. I want keep the animation smooth.
My question:
How can I get a smooth animation of the second EditText from behind the first one, changing the height of the FrameLayout as the second EditText moves down?
Thanks!
Update:
I changed the FrameLayout by a RelativeLayout I searched and there's no difference for this case. I tried scale this RelativeLayout that contains both of the EditText with an AnimationScale in order to display the animation smoothly, but didn't work. Here is my code:
protected void expanse() {
Log.d("Accordion", "expandAccordion");
//Translate the top of the second EditText to its bottom
TranslateAnimation translateSecond = new TranslateAnimation(second.getLeft(), second.getLeft(),
second.getTop(), second.getBottom());
translateSecond.setDuration(1000);
//Scale the relative layout to show the both of them
ScaleAnimation scaleContainer = new ScaleAnimation(container.getLeft(), container.getLeft(),
container.getTop(), second.getBottom());
scaleContainer.setDuration(1000);
//At the end of the scaling, change the height of the relative layout
scaleContainer.setAnimationListener(new AnimationListenerAdapter() {
#Override
public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams params = (LayoutParams) container.getLayoutParams();
params.height = second.getBottom();
container.setLayoutParams(params);
super.onAnimationEnd(animation);
}
});
container.startAnimation(scaleContainer);
second.startAnimation(translateSecond);
}
BTW I can guarantee that if I hardcode some large height like 350dp, the animation is displayed correctly.
UPDATE:
I tried moving both, the second EditText and the layout below. This is the code. BTW for another reasons I changed the ListView by a custom ViewPager, this doesn't change anything.
public class MainActivity extends FragmentActivity {
private EditText first;
private EditText second;
private HoboViewPager pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pager = (HoboViewPager) findViewById(R.id.pager);
pager.setPageTransformer(true, new RotationPageTransformer());
pager.setAdapter(new SampleAdapter(this, getSupportFragmentManager()));
first = (EditText) findViewById(R.id.location_search_field);
second = (EditText) findViewById(R.id.term_search_field);
first.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
expanseSecondField();
return true;
}
});
}
protected void expanseSecondField() {
TranslateAnimation translateSecondField = new TranslateAnimation(second.getLeft(), second.getLeft(),
second.getTop(), second.getBottom());
translateSecondField.setFillAfter(true);
translateSecondField.setDuration(1000);
TranslateAnimation translateContainer = new TranslateAnimation(pager.getLeft(), pager.getLeft(),
pager.getTop(), pager.getTop() + second.getBottom());
translateContainer.setFillAfter(true);
translateContainer.setDuration(1000);
pager.startAnimation(translateContainer);
second.startAnimation(translateSecondField);
}
}
This didn't work because the TranslateAnimation of the container is executed immediately. The result is the same that when I was trying to change the size of the container at the end of the animation.
How use the 2nd option & try performing two translate animations in tandem: 1 on the EditText, then one on the FrameLayout? Give them both the same exact deltas and duration. This way, the FrameLayout will move down smoothly, then at the end of the animation, change the height of the FrameLayout.
Hope this helps :)
So this is not a tecnical question with code but more on some thoughts. I am new to android developing and because of that I am not completely familiar with a lot of what it has to offer.
I have a list of items on the screen and you can scroll up and down to view the different items.
My issue came up when I decided to have the items be able to slide left and right to reveal a settings like panal with a few optons below them(each item has its own settings below it, not a setting screen for all items kind of deal). I looked around for different ways to do this but cant seem to find one that works. What I am thinking so far is to have a HorizonalScrollView with my item in it with the setting menu to the left or right off the screen, but when I put my text area in the HorizonalScrollView, it only takes up half the screen, so I can fit two side by side, not what I wanted and the end result wont be what I imagined. I really would like a solution that allows for the setting to be under the item, so when you push it out of the way it reveals the settings.
Is there a better way or should I just continue trying to make my HorizonalScrollView work, any guides or thought on how I could go about this would be greatly appreciated.
Went though my apps to see if I could find one that has something similar, gmail app has it. Here is a link to an image that gives you an idea, after you slide the item to the side it brings up an undo or archive buttons in the place of the item as if they were hiding under the list item you swiped away
it's quite an interesting topic this question. And be prepared to do a certain amount of custo stuff.
First of all, drop the HorizonalScrollView, I don't think it will help you. The layout of each item should be something like this (pseudo-code, you can build the XML yourself =] ):
FrameLayout
RelativeLayout id:topContent background:someSolidColor
// inside this RelativeLayout, the stuff that is visible on the ListView
RelativeLayout id:bottomContent
// inside this RelativeLayout, the stuff that is behind the content
/FrameLayout
that way you will be literally putting one thing on top of the other. Also note that the topContent have a background with a solid color. If you do not specify any background, both RelativeLayouts will be visible. Also note that I used RelativeLayout, just because I like them, and I like their flexibility, but this will depend on the content of your list view and your settings.
And now is when things get fun, you'll need a GestureDetector to detect the finger sliding and you'll use that value to generate a margin offset on the id:topContent.
You can create a TouchListener like this:
public class MySlideListener extends View.OnTouchListener{
private View v;
private GestureDetector gestureDetector;
public MySlideListener (View v){
this.v = v;
gestureDetector = new GestureDetector(v.getContext(), myGestureListener);
}
public boolean onTouch (View v, MotionEvent event){
return gestureDetector.onTouchEvent(event);
}
private SimpleOnGestureListener myGestureListener = new SimpleOnGestureListener(){
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
// now here we make the view scroll
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
lp.leftMargin = distanceX;
lp.rightMargin = -distanceX;
// You might need to call view.requestLayout();
// but first give it a try without it
// This part of the method for processing the horizontal
// offset can (and should) be further developed to add some
// 'snap-in' or transparency functionality to make the whole
// concept work better.
// But this code should give you a proof of concept on how to deal with stuff.
// The important part is that now you have a call back that have access
// to the view during onScroll.
// Also might be necessary to enable/disable the bottomContent view
// in order for it to be not clickable whilst not visible.
return true;
}
}
}
and then set a new of those listeners for each topContent of your ListView (probably inside the getView from the adapter) with topContentView.setOnTouchListener(new MySlideListener(topContentView));
Please keep in mind that I typed all this code by heart and is 100% untested!
edit:
The above code is the correct direction, but it's a 100% untested thing.
Now the code below, I've just compiled, tested, and this code works!
This class is also a bit more efficient as you can create only one and apply the same instance to all the items created on your adapter. You can see it's getting the view to scroll on the touch event.
public class MySlideListener implements View.OnTouchListener {
private View view;
private ListView listView;
private GestureDetector gestureDetector;
public MySlideListener(ListView lv) {
listView = lv;
gestureDetector = new GestureDetector(lv.getContext(), myGestureListener);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
view = v;
gestureDetector.onTouchEvent(event);
return true;
}
private SimpleOnGestureListener myGestureListener = new SimpleOnGestureListener() {
private int origLeft, origRight;
public boolean onDown(MotionEvent e) {
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
origLeft = lp.leftMargin;
origRight = lp.rightMargin;
return true;
};
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
listView.requestDisallowInterceptTouchEvent(true);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
lp.leftMargin = (int) (origLeft + (e2.getRawX() - e1.getRawX()));
lp.rightMargin = (int) (origRight - (e2.getRawX() - e1.getRawX()));
view.requestLayout();
return true;
};
};
}