EDIT: Cliffnote of what i want accomplished
straight to the point,
My XML set up is has 2 linearview (top, bottom)
1. I want my img to stay within screen when moving
2. Moving continuously randomly
3. Have it clickable/draggable
4. Have the img get its new position and move whereever it is dropped (top or bottom)
Thank you.
Hello making an app (Android Studio) for autistic children (inspired by my autistic son). In my Activity i have 2 linear layout: Top and Bottom. The top is where 3/4 of my screen that has 4 images. The bottom is the targeted drop point where the "Answer" or img should be dropped.
I have an 4 img randomly created in a (via databaseFP iterated) but now i want those img to be moving randomly within screen (top screen) and drag them into a target drag box (bottom screen). Only 1 of four img is the correct "answer" so if its "incorrect" i want this img to return to its position MOVING all the while within screen until the "correct" img is chosen to the targeted drop box. That is all...Please Please help me i have read many and not like this specifically where it involves onTouch/onDrag listener and the img goes back to position if dragged.
So far all i have done is make the img move (it retains the on click and drag but when its incorrect it goes off screen) Pls see code below
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int width = displaymetrics.widthPixels;
int height = displaymetrics.heightPixels;
Random r = new Random();
int distance = 100; //the distance to move in pixels
int duration = 500; //the duration of the animation in ms
double direction = Math.random() * 2 * Math.PI;
int translationX = (int) (Math.cos(direction) * distance);
int translationY = (int) (Math.sin(direction) * distance);
int id2 = getResources().getIdentifier(myShuffledArray[0], "drawable", getPackageName());
image.setImageResource(id2);
image.animate().translationX(translationX - width).translationY(translationY - height).setDuration(duration).start();
I am a newbie but this would help my autistic son and this is a Thesis project for my college.
EDIT this is my ontouch/drag
public boolean onDrag(View v, DragEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==DragEvent.ACTION_DROP){
//we want to make sure it is dropped only to left and right parent view
View view = (View)event.getLocalState();
//if(v.getId() == R.id.left_view || v.getId() == R.id.right_view){
if(view.getId() == R.id.box_view1){
ViewGroup source = (ViewGroup) view.getParent();
source.removeView(view);
LinearLayout target = (LinearLayout) v;
target.addView(view);
}
//make view visible as we set visibility to invisible while starting drag
view.setVisibility(View.VISIBLE);
}
return true;
}
#Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_DOWN){
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
}
return false;
}
I have found the solution posted on this website
https://blahti.wordpress.com/2011/01/17/moving-views-part-2/
Check it out for those who wants learn more on Views and Drag and Drop
Using setTranslationX, I'm trying to animate a view as I swipe it across the screen. Then after it passes a threshold-X, I assign the view a new RelativeLayout.RIGHT_OF.
I want it to stop animating (whether or not I continue swiping) at that point and basically lock to that new anchor.
This is where the problem is: suddenly the view jumps X position to the right of its new anchor.
I've tried, when it's >= threshold, to set setTranslationX(0), but then I see the view twitch/flash twice, once to its original 0, then to the new 0.
I would love to get rid of that double twitch/flash, but don't know how at this point.
#Override
public void onChildDraw(Canvas c ... float dX) {
threshold = anchorView.getRight();
if (animate) {
if (dX >= 0) {
translationX = Math.min(dX, threshold);
if (dX >= threshold) {
translationX = 0; // (A) if I do this, then mainView flashs twice: original 0, then new 0
setToRightOf(mainView, anchorView);
mainView.invalidate(); // has no effect
}
} else {
translationX = 0;
}
// if I don't do (A), then mainView will suddenly jump to 2*threshold
mainView.setTranslationX(translationX);
return;
}
super.onChildDraw(c ... dX);
}
Okay, instead of assigning RelativeLayout.RIGHT_OF during onDraw to set the threshold boundary, I took it out and assigned it when my touch left the screen.
But to insure I wouldn't swipe back behind that threshold while swiping, I had to add another case to check translationX and instead of previously trying to rely on the RelativeLayout anchor.
Now, I'm using setTag() and getTag() to help confirm the threshold during the swipe:
if (dX >= 0) {
if ((Object) past != tag)
translationX = Math.min(dX, threshold);
else
translationX = threshold;
if (dX >= threshold) {
if ((Object) past != tag) {
anchorView.setTag(past);
}
}
} else {
...
}
Plus a couple other places to make sure I reset anchorView's tag and the translationX when needed, then it's all good.
It works for now!
(doesn't directly solve the double flash/twitch issue, but a different approach to the same goal)
(any other recommendations besides using setTag()?)
P.S. In my earlier attempts, instead of invalidate(), I later tried mainView.requestLayout() with no success either, thinking requestLayout() also factors in position.
I have used custom coveflow, everything works fine when i load small amount of data, but it doesn't work with large amount of data,
check it out my below code
if (mAdapter == null || mAdapter.getCount() == 0)
throw new IllegalStateException(
"You are trying to scroll container with no adapter set. Set adapter first.");
if (mLastCenterItemIndex != -1) {
final int lastCenterItemPosition = (mFirstItemPosition + mLastCenterItemIndex)
% mAdapter.getCount();
final int di = lastCenterItemPosition - position;
final int dst = (int) (di * mCoverWidth * mSpacing);
mScrollToPositionOnNextInvalidate = -1;
scrollBy(-dst, 0);
} else {
mScrollToPositionOnNextInvalidate = position;
}
invalidate();
i have used this code to move my item to center of the tablet, now i am gng to explain my view, in one activity half of my screen occupies coverflow and other half occupies mapview, when i click map marker icon i need to move particular item to center of my screen in coverflow, so basically my coverflow and map sync, the changes must affect both,
it works perfectly when i load small amount of data, but now i tried to load 11000 records when i click marker then my UI gets blocked becuase of scrollby in Coverflow, can you suggest any idea to move my item center?? or is there any method which doesn't affect UI
All suggestion are most welcome
Thanks
Just include your code in
new Thread(new Runnable() {
public void run () {
//.........
}
}).start();
or load items by parts
I have a ViewPager with a couple of RecyclerViews as pages. I would like to implement functionality where RecyclerViews which are on other pages move by certain amount after user starts scrolling pages.
#Override
public void onPageScrolled(int position, float offset, int offsetPx) {
RecyclerView view1 = getPage(position - 1);
RecyclerView view2 = getPage(position + 1);
if(scrollNeeded()) {
view1.scrollBy(0, 200);
view2.scrollBy(0, 200);
}
}
The problem which I have is that everything works fine if I scroll slowly through my ViewPager but if I scroll crazy fast, some RecyclerViews don't get scrolled. I guess I somehow need to synchronize this method.
Any idea how to solve this problem? User shouldn't see that scroll.
ViewPager keeps +1 page left and right preloaded. Which means
in very beginning - current page and the next one
at the very end - last page and the previous one
anywhere else - current, previous and next
When user swipes really fast through pages, there is a real case where the page (your RecyclerView instance and its adapter) are still preparing, so they miss the scrollBy() call.
You can solve this in different ways.
Easiest is increasing the number of cached off screen pages (e.g. 3) by calling viewPager.setOffscreenPageLimit(3) - for more ViewPager.setOffScreenPageLimit(int). If you rely on page refreshes every time user swipes, this might be an issue.
Another option is creating a custom view for your RecyclerView page and adding a scroll value to be set from outside, e.g.
// in your custom page view
private RecyclerView.Adapter adapter;
private boolean needToScroll;
public void setNeedToScroll(boolean needToScroll) {
this.needToScroll = needToScroll;
// if adapter is not null (i.e. already set), scroll as is
// and set the value to false
if (adapter != null) {
this.needToScroll = false;
scrollBy(0, 200);
}
}
// and then in the place where you define your adapter, but after setting it
if (needToScroll) {
needToScroll = false;
scrollBy(0, 200);
}
Finally your view pager scroll listener
#Override
public void onPageScrolled(int position, float offset, int offsetPx) {
if(scrollNeeded()) {
Page view1 = getPage(position - 1);
Page view2 = getPage(position + 1);
view1.needToScroll(true);
view2.needToScroll(true);
}
}
The browsers in Android 2.3+ do a good job at maintaining the scrolled position of content on an orientation changed.
I'm trying to achieve the same thing for a WebView which I display within an activity but without success.
I've tested the manifest change (android:configChanges="orientation|keyboardHidden") to avoid activity recreation on a rotation however because of the different aspect ratios the scroll position does not get set to where I want. Furthermore this is not a solution for me as I need to have different layouts in portrait and landscape.
I've tried saving the WebView state and restoring it but this resuls in the content being displayed at the top again.
Furthermore attempting to scroll in onPageFinished using scrollTo doesn't work even though the height of the WebView is non-zero at this point.
Any ideas? Thanks in advance. Peter.
Partial Solution:
My colleague managed to get scrolling working via a javascript solution. For simple scrolling to the same vertical position, the WebView's 'Y' scroll position is saved in onSaveInstanceState state. The following is then added to onPageFinished:
public void onPageFinished(final WebView view, final String url) {
if (mScrollY > 0) {
final StringBuilder sb = new StringBuilder("javascript:window.scrollTo(0, ");
sb.append(mScrollY);
sb.append("/ window.devicePixelRatio);");
view.loadUrl(sb.toString());
}
super.onPageFinished(view, url);
}
You do get the slight flicker as the content jumps from the beginning to the new scroll position but it is barely noticeable. The next step is try a percentage based method (based on differences in height) and also investigate having the WebView save and restore its state.
To restore the current position of a WebView during orientation change I'm afraid you will have to do it manually.
I used this method:
Calculate actual percent of scroll in the WebView
Save it in the onRetainNonConfigurationInstance
Restore the position of the WebView when it's recreated
Because the width and height of the WebView is not the same in portrait and landscape mode, I use a percent to represent the user scroll position.
Step by step:
1) Calculate actual percent of scroll in the WebView
// Calculate the % of scroll progress in the actual web page content
private float calculateProgression(WebView content) {
float positionTopView = content.getTop();
float contentHeight = content.getContentHeight();
float currentScrollPosition = content.getScrollY();
float percentWebview = (currentScrollPosition - positionTopView) / contentHeight;
return percentWebview;
}
2) Save it in the onRetainNonConfigurationInstance
Save the progress just before the orientation change
#Override
public Object onRetainNonConfigurationInstance() {
OrientationChangeData objectToSave = new OrientationChangeData();
objectToSave.mProgress = calculateProgression(mWebView);
return objectToSave;
}
// Container class used to save data during the orientation change
private final static class OrientationChangeData {
public float mProgress;
}
3) Restore the position of the WebView when it's recreated
Get the progress from the orientation change data
private boolean mHasToRestoreState = false;
private float mProgressToRestore;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mWebView = (WebView) findViewById(R.id.WebView);
mWebView.setWebViewClient(new MyWebViewClient());
mWebView.loadUrl("http://stackoverflow.com/");
OrientationChangeData data = (OrientationChangeData) getLastNonConfigurationInstance();
if (data != null) {
mHasToRestoreState = true;
mProgressToRestore = data.mProgress;
}
}
To restore the current position you will have to wait the page to be reloaded (
this method can be problematic if your page takes a long time to load)
private class MyWebViewClient extends WebViewClient {
#Override
public void onPageFinished(WebView view, String url) {
if (mHasToRestoreState) {
mHasToRestoreState = false;
view.postDelayed(new Runnable() {
#Override
public void run() {
float webviewsize = mWebView.getContentHeight() - mWebView.getTop();
float positionInWV = webviewsize * mProgressToRestore;
int positionY = Math.round(mWebView.getTop() + positionInWV);
mWebView.scrollTo(0, positionY);
}
// Delay the scrollTo to make it work
}, 300);
}
super.onPageFinished(view, url);
}
}
During my test I encounter that you need to wait a little after the onPageFinished method is called to make the scroll working. 300ms should be ok. This delay make the display to flick (first display at scroll 0 then go to the correct position).
Maybe there is an other better way to do it but I'm not aware of.
Why you should consider this answer over accepted answer:
Accepted answer provides decent and simple way to save scroll position, however it is far from perfect. The problem with that approach is that sometimes during rotation you won't even see any of the elements you saw on the screen before rotation. Element that was at the top of the screen can now be at the bottom after rotation. Saving position via percent of scroll is not very accurate and on large documents this inaccuracy can add up.
So here is another method: it's way more complicated, but it almost guarantees that you'll see exactly the same element after rotation that you saw before rotation. In my opinion, this leads to a much better user experience, especially on a large documents.
======
First of all, we will track current scroll position via javascript. This will allow us to know exactly which element is currently at the top of the screen and how much is it scrolled.
First, ensure that javascript is enabled for your WebView:
webView.getSettings().setJavaScriptEnabled(true);
Next, we need to create java class that will accept information from within javascript:
public class WebScrollListener {
private String element;
private int margin;
#JavascriptInterface
public void onScrollPositionChange(String topElementCssSelector, int topElementTopMargin) {
Log.d("WebScrollListener", "Scroll position changed: " + topElementCssSelector + " " + topElementTopMargin);
element = topElementCssSelector;
margin = topElementTopMargin;
}
}
Then we add this class to WebView:
scrollListener = new WebScrollListener(); // save this in an instance variable
webView.addJavascriptInterface(scrollListener, "WebScrollListener");
Now we need to insert javascript code into html page. This script will send scroll data to java (if you are generation html, just append this script; otherwise, you might need to resort to calling document.write() via webView.loadUrl("javascript:document.write(" + script + ")");):
<script>
// We will find first visible element on the screen
// by probing document with the document.elementFromPoint function;
// we need to make sure that we dont just return
// body element or any element that is very large;
// best case scenario is if we get any element that
// doesn't contain other elements, but any small element is good enough;
var findSmallElementOnScreen = function() {
var SIZE_LIMIT = 1024;
var elem = undefined;
var offsetY = 0;
while (!elem) {
var e = document.elementFromPoint(100, offsetY);
if (e.getBoundingClientRect().height < SIZE_LIMIT) {
elem = e;
} else {
offsetY += 50;
}
}
return elem;
};
// Convert dom element to css selector for later use
var getCssSelector = function(el) {
if (!(el instanceof Element))
return;
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
var selector = el.nodeName.toLowerCase();
if (el.id) {
selector += '#' + el.id;
path.unshift(selector);
break;
} else {
var sib = el, nth = 1;
while (sib = sib.previousElementSibling) {
if (sib.nodeName.toLowerCase() == selector)
nth++;
}
if (nth != 1)
selector += ':nth-of-type('+nth+')';
}
path.unshift(selector);
el = el.parentNode;
}
return path.join(' > ');
};
// Send topmost element and its top offset to java
var reportScrollPosition = function() {
var elem = findSmallElementOnScreen();
if (elem) {
var selector = getCssSelector(elem);
var offset = elem.getBoundingClientRect().top;
WebScrollListener.onScrollPositionChange(selector, offset);
}
}
// We will report scroll position every time when scroll position changes,
// but timer will ensure that this doesn't happen more often than needed
// (scroll event fires way too rapidly)
var previousTimeout = undefined;
window.addEventListener('scroll', function() {
clearTimeout(previousTimeout);
previousTimeout = setTimeout(reportScrollPosition, 200);
});
</script>
If you run your app at this point, you should already see messages in logcat telling you that the new scroll position is received.
Now we need to save webView state:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
webView.saveState(outState);
outState.putString("scrollElement", scrollListener.element);
outState.putInt("scrollMargin", scrollListener.margin);
}
Then we read it in the onCreate (for Activity) or onCreateView (for fragment) method:
if (savedInstanceState != null) {
webView.restoreState(savedInstanceState);
initialScrollElement = savedInstanceState.getString("scrollElement");
initialScrollMargin = savedInstanceState.getInt("scrollMargin");
}
We also need to add WebViewClient to our webView and override onPageFinished method:
#Override
public void onPageFinished(final WebView view, String url) {
if (initialScrollElement != null) {
// It's very hard to detect when web page actually finished loading;
// At the time onPageFinished is called, page might still not be parsed
// Any javascript inside <script>...</script> tags might still not be executed;
// Dom tree might still be incomplete;
// So we are gonna use a combination of delays and checks to ensure
// that scroll position is only restored after page has actually finished loading
webView.postDelayed(new Runnable() {
#Override
public void run() {
String javascript = "(function ( selectorToRestore, positionToRestore ) {\n" +
" var previousTop = 0;\n" +
" var check = function() {\n" +
" var elem = document.querySelector(selectorToRestore);\n" +
" if (!elem) {\n" +
" setTimeout(check, 100);\n" +
" return;\n" +
" }\n" +
" var currentTop = elem.getBoundingClientRect().top;\n" +
" if (currentTop !== previousTop) {\n" +
" previousTop = currentTop;\n" +
" setTimeout(check, 100);\n" +
" } else {\n" +
" window.scrollBy(0, currentTop - positionToRestore);\n" +
" }\n" +
" };\n" +
" check();\n" +
"}('" + initialScrollElement + "', " + initialScrollMargin + "));";
webView.loadUrl("javascript:" + javascript);
initialScrollElement = null;
}
}, 300);
}
}
This is it. After screen rotation, element that was at the top of your screen should now remain there.
onPageFinished may not be called because you are not reloading the page, you are just changing the orientation, not sure if this causes a reload or not.
Try using scrollTo in the onConfigurationChanged method of your activity.
The aspect change will most likely always cause the current location in your WebView to not be the right location to scroll to afterwards. You could be sneaky and determine the top most visible element in the WebView and after an orientation change implant an anchor at that point in the source and redirect the user to it...
to calculate the right percentage of WebView, it is important to count mWebView.getScale() also. Actually, return value of getScrollY() is respectively with mWebView.getContentHeight()*mWebView.getScale().
In the partial solution described in the original post, you can improve the solution if change the following code
private float calculateProgression(WebView content) {
float contentHeight = content.getContentHeight();
float currentScrollPosition = content.getScrollY();
float percentWebview = currentScrollPosition/ contentHeight;
return percentWebview;
}
due to the top position of the component, when you are using content.getTop () is not necessary; So what it does is add to the actual position of the scroll Y position where the component is located with respect to it parent and runs down the content. I hope my explanation is clear.
The way that we have managed to achieve this to have a local reference to the WebView within our Fragments.
/**
* WebView reference
*/
private WebView webView;
Then setRetainInstance to true in onCreate
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
// Argument loading settings
}
Then when onCreateView is called, return the existing local instance of the WebView, otherwise instantiate a new copy setting up the content and other settings. The key step he is when reattaching the WebView to remove the parent which you can see in the else clause below.
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View view;
if (webView == null) {
view = inflater.inflate(R.layout.webview_dialog, container);
webView = (WebView) view.findViewById(R.id.dialog_web_view);
// Setup webview and content etc...
} else {
((ViewGroup) webView.getParent()).removeView(webView);
view = webView;
}
return view;
}
I used the partial solution described in the original post, but instead put the javascript snippet inside onPageStarted(), instead of onPageFinished(). Seems to work for me with no jumping.
My use case is slightly different: I'm trying to keep the horizontal position after the user refreshes the page.
But I've been able to use your solution and it works perfectly for me :)