I am trying to implement the ParallaxHeaderViewPager like here. But instead using a KenBurnsSupportView and an ImageView as a Header i have used a FrameLayout that contains a ViewPager. My problem is that if i try to use vertical scroll on the Header the Event gets consumed but nothing happens. I can only scroll horizontally on the Header(ViewPager) and the Page changes correctly. But this is not what i want to accomplish. I want if i scroll vertically on the Header the event to be recognized as a ListView scroll and when i scroll horizontally (on the Header) as a ViewPager scroll. The scrolling is functioning correctly on the area that is not used from the header. Namely under the header where i have 4 Listviews I can scroll both vertically and horizontally. The Layout I am currently using is like the below code snippet:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="#dimen/header_height" >
<FrameLayout
android:id="#+id/news_pager_content_area"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="#color/white"
/>
<com.astuetz.PagerSlidingTabStrip
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="48dip"
android:layout_gravity="bottom"
android:background="#android:color/transparent" />
</FrameLayout>
I have tried to subclass both the Listviews and the Viewpager to intercept and handle touch Events but it seems that the default behavior documented in the Android Documentation is not applicable in my example. Thank you for your help.
I solved my question trying all the answers at similar topics on Stackoverflow and looking at the Android Documentation again. I created a custom FrameLayout for the root container of the above Layout file. I override the dispatchTouchEvent method.
public boolean dispatchTouchEvent(MotionEvent ev)
{
switch (ev.Action)
{
case MotionEventActions.Down:
_xDistance = _yDistance = 0f;
_lastX = ev.GetX();
_lastY = ev.GetY();
_startY = _lastY;
_isFirstTime = true;
_downEvent = MotionEvent.obtain(ev);
break;
case MotionEventActions.Move:
float curX = ev.GetX();
float curY = ev.GetY();
_xDistance += Math.abs(curX - _lastX);
_yDistance += Math.abs(curY - _lastY);
_lastX = curX;
_lastY = curY;
float yDeltatTotal = curY - _startY;
if (_yDistance > _xDistance && Math.abs(yDeltatTotal) > _touchSlop)
{
if (_isFirstTime)
{
_isFirstTime = false;
return childView.dispatchTouchEvent(_downEvent);
}
return childView.dispatchTouchEvent(ev);
}
}
return super.DispatchTouchEvent(ev);
}
Where as ChildView I set the ViewPager with id = pager from the layout above. To avoid pointerindex out of range Exception I used the obtain method as specified by Dmitry Zaitsev in the first comment at this link.
Related
I have a RecyclerView inside a ViewPager that only occupies the bottom half of the screen, and what I want to do is have the entire screen scroll if the RecyclerViews received a vertical scroll event.
UI hierarchy in a nutshell:
CoordinatorLayout
--- AppBarLayout
--- Toolbar
--- Bunch of static LinearLayouts, occupy most of screen
--- TabLayout
--- ViewPager
--- Fragments with RecyclerView
What I have:
To my understanding, a RecyclerView is memory-efficient and tries to fit itself in whatever space is available in the screen. In my app, I have a ViewPager hosting multiple tabs, each of which have a RecyclerView to display different things.
Could you please give me some ideas of what I could do to make the whole screen scroll? I'm guessing the best shot is to add a CollapsingToolbarLayout with parallax to the static content in the middle of the screen. I even tried to do this, but the UI was completely broken. It's difficult since I only want the content in the middle of the screen to scroll out, not the toolbars on top. I didn't have much luck with a NestedScrollView either, apparently its compatibility with ViewPager and CoordinatorLayout is not straight-forward..
What I want:
Main Activity Layout:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar_contributor"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:focusableInTouchMode="true">
<android.support.v7.widget.SearchView
android:id="#+id/searchview_contributor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:icon="#drawable/ic_search_white_24dp"
app:defaultQueryHint="Bla bla"
app:iconifiedByDefault="false"/>
<ImageButton
android:layout_width="24dp"
android:layout_height="24dp"
android:background="#android:color/transparent"
android:src="#drawable/ic_person_outline_black_24dp"/>
</android.support.v7.widget.Toolbar>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:text="La Bla"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:text="Bla Bla"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
>
<!-- Bunch of stuff, but irrelevant -->
<android.support.design.widget.TabLayout
android:id="#+id/tablayout_profile"
android:layout_width="match_parent"
android:layout_height="40dp"/>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager_profile"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
The fragment inside the Viewpager:
<FrameLayout
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">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview_photos"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Thanks in advance!
Update: The responses I received here (for which I am thankful) came a bit too late. If anyone knows which is the correct answer, please let me know!
public class CustomRecyclerView extends RecyclerView {
public CustomRecyclerView(Context context) {
super(context);
}
public CustomRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
int lastEvent = -1;
boolean isLastEventIntercepted = false;
private float xDistance, yDistance, lastX, lastY;
#Override
public boolean onInterceptTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = e.getX();
lastY = e.getY();
break;
case MotionEvent.ACTION_MOVE:
final float curX = e.getX();
final float curY = e.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (isLastEventIntercepted && lastEvent == MotionEvent.ACTION_MOVE) {
return false;
}
if (xDistance > yDistance) {
isLastEventIntercepted = true;
lastEvent = MotionEvent.ACTION_MOVE;
return false;
}
}
lastEvent = e.getAction();
isLastEventIntercepted = false;
return super.onInterceptTouchEvent(e);
}
}
Just change your recyclerview by this .
I've done something very similar to this with a CollapsingToolbarLayout.
The trick here is to have two toolbars, one at the top with the searchview and another that is the toolbar dedicated to the collapsing layout.
Your searchview toolbar will have app:collapseMode="pin".
Your button bar will have app:collapseMode="pin".
Your LinearLayout below the button bar will have the default collapse mode so it will scroll. You can even give it a parallax effect with app:collapseMode="parallax".
Your TabLayout will have app:collapseMode="pin" so it scrolls up under the button bar and pins there.
Your ViewPager should be outside the CollapsingToolbarLayout and have the attribute app:layout_behavior="#string/appbar_scrolling_view_behavior".
The CollapsingToolbarLayout will want to display a title. Just call setTitle(null) on the collapsing toolbar and set your real title on the searchview toolbar.
You can put your navigation and menus on either the searchview toolbar and the collapsing toolbar and they should work as expected.
I will post some XML later; I just wanted to get this answer started.
It looks like you simply miss the app:layout_scrollFlags attribute. You should add it both to the Toolbar and your linear header layouts. For example, you could use: app:layout_scrollFlags="scroll|enterAlways"
As I see, your ViewPager already has app:layout_behavior="#string/appbar_scrolling_view_behavior", sou it should work right after you specify the scrollFlags
The Objective:
I am writing a view that behaves like a RecyclerView inside the ViewPager i-e; The view moves when the finger is moved horizontally and RecyclerView scrolls when the finger is moved vertically. Please note that my custom view has another objective while the finger is moving horizontally, so therefore I can't use viewpager here.
The Problem:
The overlay view handles the horizontal finger motion movement properly, however the vertical event when passed down to the RecyclerView doesn't result in scrolling.
The Question:
What should I do that the vertical finger movement is passed to the RecyclerView and result in Scrolling?
The Code:
mOverlayView.setOnTouchListener(new View.OnTouchListener() {
switch (action) {
case MotionEvent.ACTION_UP:
//some code
return true;
case MotionEvent.ACTION_DOWN:
mBackupTouchDownEvent = event;
// some code
return true;
case MotionEvent.ACTION_MOVE:
// some logic {
movingHorizontally = true;
}
return true;
} else
movingHorizontally = false;
break;
}
// executor came down here? This means finger moved vertically
mRecyclerView.requestFocus();
mRecyclerView.dispatchTouchEvent(mBackupTouchDownEvent); // don't cause scrolling
mRecyclerView.dispatchTouchEvent(event); // don't cause scrolling? Alternative solutoin?
return false;
}
XML:
<RelativeLayout
android:background="#color/colorPrimary"
android:layout_width="400dp"
android:layout_height="500dp"
android:layout_centerVertical="true"
>
<android.support.v7.widget.RecyclerView
android:layout_width="380dp"
android:layout_height="match_parent"
android:id="#+id/scrollView">
</android.support.v7.widget.RecyclerView>
<RelativeLayout
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="#77555555"
android:id="#+id/overlayScreen"/>
</RelativeLayout>
This is the app I'm trying to build with all the elements mapped out below:
Everything works, however, I want the inner horizontal recyclerview not to capture any of the vertical scrolls. All vertical scrolls must go towards the outer vertical recyclerview, not the horizontal one, so that the vertical scroll would allow for the toolbar to exit out of view according to it's scrollFlag.
When I put my finger on the "StrawBerry Plant" part of the recyclerview and scroll up, it scroll out the toolbar:
If I put my finger on the horizontal scrollview and scroll up, it does not scroll out the toolbar at all.
The following is my xml layout code so far.
The Activity xml layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container"
android:clipChildren="false">
<android.support.design.widget.CoordinatorLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/container"
>
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="#+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
style="#style/CustomTabLayout"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
The "Fruits" fragment xml layout (which is the code for the fragment - the fragment is labeled in the above picture):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/progressBar"
android:visibility="gone"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<!-- <android.support.v7.widget.RecyclerView-->
<com.example.simon.customshapes.VerticallyScrollRecyclerView
android:id="#+id/main_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
I have used a custom class called VerticallyScrollRecyclerView which follows google example of handling touch events in a viewgroup. Its aim is to intercept and consume all the vertical scroll events so that it will scroll in / out the toolbar: http://developer.android.com/training/gestures/viewgroup.html
The code for VerticallyScrollRecyclerView is below:
public class VerticallyScrollRecyclerView extends RecyclerView {
public VerticallyScrollRecyclerView(Context context) {
super(context);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticallyScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
ViewConfiguration vc = ViewConfiguration.get(this.getContext());
private int mTouchSlop = vc.getScaledTouchSlop();
private boolean mIsScrolling;
private float startY;
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
// Always handle the case of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the scroll.
mIsScrolling = false;
startY = ev.getY();
return super.onInterceptTouchEvent(ev); // Do not intercept touch event, let the child handle it
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
Log.e("VRecView", "its moving");
if (mIsScrolling) {
// We're currently scrolling, so yes, intercept the
// touch event!
return true;
}
// If the user has dragged her finger horizontally more than
// the touch slop, start the scroll
// left as an exercise for the reader
final float yDiff = calculateDistanceY(ev.getY());
Log.e("yDiff ", ""+yDiff);
// Touch slop should be calculated using ViewConfiguration
// constants.
if (Math.abs(yDiff) > 5) {
// Start scrolling!
Log.e("Scroll", "we are scrolling vertically");
mIsScrolling = true;
return true;
}
break;
}
}
return super.onInterceptTouchEvent(ev);
}
private float calculateDistanceY(float endY) {
return startY - endY;
}
}
The "Favourite" layout which is the recyclerview within the vertical recyclerview:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#color/white"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Favourite"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:id="#+id/header_fav"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="#+id/header_fav"
android:id="#+id/recyclerview_fav">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
This has been bugging me for a while now and I have not managed to come up with a solution. Does anyone know how to solve this problem?
5 points to Griffindor for the correct answer and of course, reputation points on SO.
Tested solution:
All you need is to call mInnerRecycler.setNestedScrollingEnabled(false); on your inner RecyclerViews
Explanation:
RecyclerView has support for nested scrolling introduced in API 21 through implementing the NestedScrollingChild interface. This is a valuable feature when you have a scrolling view inside another one that scrolls in the same direction and you want to scroll the inner View only when focused.
In any case, RecyclerView by default calls RecyclerView.setNestedScrollingEnabled(true); on itself when initializing. Now, back to the problem, since both of your RecyclerViews are within the same ViewPager that has the AppBarBehavior, the CoordinateLayout has to decide which scroll to respond to when you scroll from your inner RecyclerView; when your inner RecyclerView's nested scrolling is enabled, it gets the scrolling focus and the CoordinateLayout will choose to respond to its scrolling over the outer RecyclerView's scrolling. The thing is that, since your inner RecyclerViews don't scroll vertically, there is no vertical scroll change (from the CoordinateLayout's point of view), and if there is no change, the AppBarLayout doesn't change either.
In your case, because your inner RecyclerViews are scrolling in a different direction, you can disable it, thus causing the CoordinateLayout to disregard its scrolling and respond to the outer RecyclerView's scrolling.
Notice:
The xml attribute android:nestedScrollingEnabled="boolean" is not intended for use with the RecyclerView, and an attempt to use android:nestedScrollingEnabled="false" will result in a java.lang.NullPointerException so, at least for now, you will have to do it in code.
if any one still looking , try this :
private val Y_BUFFER = 10
private var preX = 0f
private var preY = 0f
mView.rv.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
when (e.action) {
MotionEvent.ACTION_DOWN -> rv.parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_MOVE -> {
if (Math.abs(e.x - preX) > Math.abs(e.y - preY)) {
rv.parent.requestDisallowInterceptTouchEvent(true)
} else if (Math.abs(e.y - preY) > Y_BUFFER) {
rv.parent.requestDisallowInterceptTouchEvent(false)
}
}
}
preX = e.x
preY = e.y
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
})
it checks if currently scrolling horizontal then don't allow parent to handel event
I am a bit late but this will defintly work for others facing the same problem
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int action = e.getAction();
// Toast.makeText(getActivity(),"HERE",Toast.LENGTH_SHORT).show();
switch (action) {
case MotionEvent.ACTION_POINTER_UP:
rv.getParent().requestDisallowInterceptTouchEvent(true);
break;
}
return false;
}
Tested solution, use a custom NestedScrollView().
Code:
public class CustomNestedScrollView extends NestedScrollView {
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Explicitly call computeScroll() to make the Scroller compute itself
computeScroll();
}
return super.onInterceptTouchEvent(ev);
}
}
try
public OuterRecyclerViewAdapter(List<Item> items) {
//Constructor stuff
viewPool = new RecyclerView.RecycledViewPool();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create viewHolder etc
holder.innerRecyclerView.setRecycledViewPool(viewPool);
}
inner recylerview will use the same viewpool and it'll be smoother
I would suggest you add the horizontal recyclerview inside fragments like the google app
i read through the offered answers of using setNestedScrollingEnabled to false and it was awful for me as it makes the recyclerview not recycle and you can get crashes in memory, performance issues maybe etc if you have a huge list. so i will give you a algorithm to make it work without any code.
have a listener on the vertical recyclerview, such as a scroll listener. anytime the list is scrolled you will get a callback that its being scrolled. you should also get a call back when its idle.
now when vertical recylerview is being scrolled, setNestedScrollingEnabled = false on the horizontal list
once vertical recyclerview is idle setNestedScrollingEnabled = true on the same horizontal list.
also initially set the horizontal recyclerview to setNestedScrollingEnabled = false in xml
this can also work great also with appbarlayout when coordinatorLayout gets confused with two recyclerviews in different directions, but thats another question.
lets take a look at another more simple way in kotlin, to get this done with a real example. here we will focus just on the horizontal recyclerView:
assume we have RecyclerViewVertical & RecyclerViewHorizontal:
RecyclerViewHorizontal.apply {
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
isNestedScrollingEnabled = RecyclerView.SCROLL_STATE_IDLE != newState
}
})
}
what this code says is if RecyclerViewHorizontal is not idle then enable nestedScrolling, otherwise disable it. That means when its idle we can now use the RecyclerViewVertical in a coordinatorlayout without any interference from the RecyclerViewHorizontal since we have disabled it when its idle.
I know how to display an image that is bigger than the screen. That's fairly simple and explained in details here, however, this method makes you able to scroll as far as you want, event if you have left the image and you just have black screen. I would like to know how we can make the scrolling stop when we reach the side of the picture...
Load your image into a WebView. Then you will get all the scroll behaviors you are looking for, and even the default zoom controls if you choose to turn them on...practically for free. Your only other option (to my knowledge) would be to used the 2D graphics APIs (Canvas, etc.) and create your own version of drawing tiles (like viewing a section of a map).
If your image is local, take a look at this example of reading local image data in from the SD Card. The cleaner approach to serving a local image in this case would be to create a ContentProvider and access the resource through a content:// URL in your WebView.
Example using the bundled image car.jpg in the assets directory of your project:
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<WebView
android:id="#+id/web"
android:layout_width="150dip"
android:layout_height="150dip"
/>
</LinearLayout>
src/ImageViewer.java
public class ImageViewer extends Activity {
WebView webView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
webView = (WebView)findViewById(R.id.web);
webView.loadUrl("file:///android_asset/car.jpg");
}
}
For an image bundled with your project, that's the simplest method. This does not work with images in your resources directory (like res/drawable). That path is not worth the code required to complete it.
In your Activity.onCreate():
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(...);
I was not sure which layout you exactly mean so I am posting 2 versions:
Version 1 (Buton below the image):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<ScrollView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_weight="1"
android:fadingEdge="none" android:scrollbars="none">
<ImageView android:id="#+id/image" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:scaleType="center" />
</ScrollView>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="OK"
android:layout_gravity="center" />
</LinearLayout>
Version 2 (Button over the image - in z axis):
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<ScrollView android:layout_width="fill_parent"
android:layout_height="fill_parent" android:fadingEdge="none"
android:scrollbars="none">
<ImageView android:id="#+id/image" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:scaleType="center" />
</ScrollView>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="OK"
android:layout_gravity="bottom|center_horizontal" />
</FrameLayout>
You can also try putting ScrollView inside a HorizontalScrollView.
Well I had same issue here is my solution:
Display display = getWindowManager().getDefaultDisplay();
screenSize = new Point();
display.getSize(screenSize);
Get screen size it will be required to set bottom and right boundaries
image.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
float curX, curY;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mx = event.getX();
my = event.getY();
rect = image.getDrawable().getBounds();
break;
case MotionEvent.ACTION_MOVE:
curX = event.getX();
curY = event.getY();
image.scrollBy(getX((int) (mx - curX)), getY((int) (my - curY)));
mx = curX;
my = curY;
break;
}
return true;
}
});
and finally two methods to set new scroll coordinates
private int getX(int x){
if (image.getScrollX() + x < 0 ){ //left side
return 0;
} else if (image.getScrollX() + x >= (rect.right - screenSize.x)){ //right side
return 0;
} else {
return x;
}
}
private int getY(int y){
if (image.getScrollY() + y < 0 ){ //top side
return 0;
} else if (image.getScrollY() + y >= (rect.bottom - screenSize.y)){ //bottom side
return 0;
} else {
return y;
}
}
Works great API 16+, hope it will help
I want to implement an activity where the only thing you see is a big image, which can be scrolled horizontally and vertically.On Top of that image I want to display buttons, that can be clicked and trigger certain actions (like creating an intent to start a new activity).
First I was thinking about a ScrollView, that has a FrameLayout as a child. The FrameLayout could have the image as a background and can have the buttons as childs. Because I know the position of my buttons exactly I could place them with absolute coordinates. Here is my first code:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:layout_width="1298px"
android:layout_height="945px"
android:background="#drawable/myimage">
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="115px"
android:layout_y="128px"/>
</FrameLayout>
</ScrollView>
The Problem is, that you can only scroll a ScrollView vertically. HorizontalScrollView doesn't solve the Problem, cause it only scrolls in one direction either. Can I mix them somehow? Is there another solution?
I found some similar threads on stackoverflow, where people put the image into a WebView and get horizonzal/vertical scrolling for free (here). Or someone put the image in an imageview and gave the imageview an onTouchListener to handle scrolling (here). The Problem with both ways is, that I either way I dont think you can put Buttons on top of the image, which is what I need to do.
I would very appreciate if someone help me out.
Using the (deprecated!!!) AbsoluteLayout and giving it and onTouchListener solved my problem:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:src="#drawable/myImage"
android:layout_width="1298px"
android:layout_height="945px"
android:layout_x="0px"
android:layout_y="0px" />
<Button
android:id="#+id/myButton"
android:text="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="50px"
android:layout_y="300px"
android:tag="1"/>
</AbsoluteLayout>
private float mx;
private float my;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout);
final Button button = (Button)findViewById(R.id.myButton);
button.setOnClickListener (new View.OnClickListener(){
// OnClickAction
});
final AbsoluteLayout switcherView = (AbsoluteLayout) this.findViewById(R.id.myLayout);
switcherView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View arg0, MotionEvent event) {
float curX, curY;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mx = event.getX();
my = event.getY();
break;
case MotionEvent.ACTION_MOVE:
curX = event.getX();
curY = event.getY();
switcherView.scrollBy((int) (mx - curX), (int) (my - curY));
mx = curX;
my = curY;
break;
case MotionEvent.ACTION_UP:
curX = event.getX();
curY = event.getY();
switcherView.scrollBy((int) (mx - curX), (int) (my - curY));
break;
}
return true;
}
});
}