I was trying to adapt a 2dscrollview library, and ran into a problem. I get the following when I try to compile using the library.
Here is the Java code I was trying to adapt into a library.
I also get some weird errors when b4a tries to parse the XML file with the library about duplicate definitions. I take out the duplicates, and then it seems to parse ok. Any ideas what I'm doing wrong?
Compiling code. 0.31
Generating R file. 0.03
Compiling generated Java code. Error
B4A line: 11
Dim twods As TwoDScrollView
javac 1.6.0_21
src\mindware\twods\main.java:297: cannot find symbol
symbol : constructor TwoDScrollView()
location: class us.gorges.my_package.TwoDScrollView
mostCurrent._twods = new us.gorges.my_package.TwoDScrollView();
^
1 error
Here is the Java code I was trying to adapt into a library.
B4X:
Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Updated 2010 for Grammercy Consultants by GORGES
* http://GORGES.us
*/
package us.gorges.my_package;
import anywheresoftware.b4a.BA.ShortName;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;
/**
* Layout container for a view hierarchy that can be scrolled by the user,
* allowing it to be larger than the physical display. A TwoDScrollView
* is a {@link FrameLayout}, meaning you should place one child in it
* containing the entire contents to scroll; this child may itself be a layout
* manager with a complex hierarchy of objects. A child that is often used
* is a {@link LinearLayout} in a vertical orientation, presenting a vertical
* array of top-level items that the user can scroll through.
*
* <p>The {@link TextView} class also
* takes care of its own scrolling, so does not require a TwoDScrollView, but
* using the two together is possible to achieve the effect of a text view
* within a larger container.
*
* <p>TwoDScrollView only supports vertical scrolling.
*/
@ShortName("TwoDScrollView")
public class TwoDScrollView extends FrameLayout {
static final int ANIMATED_SCROLL_GAP = 250;
static final float MAX_SCROLL_FACTOR = 0.5f;
private long mLastScroll;
private Scroller mScroller;
/**
* Position of the last motion event.
*/
private float mLastMotionY;
private float mLastMotionX;
/**
* True if the user is currently dragging this TwoDScrollView around. This is
* not the same as 'is being flinged', which can be checked by
* mScroller.isFinished() (flinging begins when the user lifts his finger).
*/
private boolean mIsBeingDragged = false;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
/**
* Whether arrow scrolling is animated.
*/
private int mTouchSlop;
private int mMinimumVelocity;
//private int mMaximumVelocity;
public TwoDScrollView(Context context) {
super(context);
initTwoDScrollView();
}
public TwoDScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
initTwoDScrollView();
}
public TwoDScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initTwoDScrollView();
}
@Override
protected float getTopFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getVerticalFadingEdgeLength();
if (getScrollY() < length) {
return getScrollY() / (float) length;
}
return 1.0f;
}
@Override
protected float getBottomFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getVerticalFadingEdgeLength();
final int bottomEdge = getHeight() - getPaddingBottom();
final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
if (span < length) {
return span / (float) length;
}
return 1.0f;
}
@Override
protected float getLeftFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getHorizontalFadingEdgeLength();
if (getScrollX() < length) {
return getScrollX() / (float) length;
}
return 1.0f;
}
@Override
protected float getRightFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getHorizontalFadingEdgeLength();
final int rightEdge = getWidth() - getPaddingRight();
final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
if (span < length) {
return span / (float) length;
}
return 1.0f;
}
private void initTwoDScrollView() {
mScroller = new Scroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
//mMaximumVelocity = mMinimumVelocity * 5;//configuration.getScaledMaximumFlingVelocity();
}
@Override
public void addView(View child) {
if (getChildCount() > 0) {
throw new IllegalStateException("TwoDScrollView can host only one direct child");
}
super.addView(child);
}
@Override
public void addView(View child, int index) {
if (getChildCount() > 0) {
throw new IllegalStateException("TwoDScrollView can host only one direct child");
}
super.addView(child, index);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("TwoDScrollView can host only one direct child");
}
super.addView(child, params);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("TwoDScrollView can host only one direct child");
}
super.addView(child, index, params);
}
/**
* @return Returns true this TwoDScrollView can be scrolled
*/
private boolean canScroll() {
View child = getChildAt(0);
if (child != null) {
int childHeight = child.getHeight();
int childWidth = child.getWidth();
return (getHeight() < childHeight + getPaddingTop() + getPaddingBottom()) ||
(getWidth() < childWidth + getPaddingLeft() + getPaddingRight());
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
if (!canScroll()) {
mIsBeingDragged = false;
return false;
}
final float y = ev.getY();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_MOVE:
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int yDiff = (int) Math.abs(y - mLastMotionY);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (yDiff > mTouchSlop || xDiff > mTouchSlop) {
mIsBeingDragged = true;
}
break;
case MotionEvent.ACTION_DOWN:
/* Remember location of down touch */
mLastMotionY = y;
mLastMotionX = x;
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
}
if (!canScroll()) {
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float y = ev.getY();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionY = y;
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
// Scroll to follow the motion event
int deltaX = (int) (mLastMotionX - x);
int deltaY = (int) (mLastMotionY - y);
mLastMotionX = x;
mLastMotionY = y;
if (deltaX < 0) {
if (getScrollX() < 0) {
deltaX = 0;
}
} else if (deltaX > 0) {
final int rightEdge = getWidth() - getPaddingRight();
final int availableToScroll = getChildAt(0).getRight() - getScrollX() - rightEdge;
if (availableToScroll > 0) {
deltaX = Math.min(availableToScroll, deltaX);
} else {
deltaX = 0;
}
}
if (deltaY < 0) {
if (getScrollY() < 0) {
deltaY = 0;
}
} else if (deltaY > 0) {
final int bottomEdge = getHeight() - getPaddingBottom();
final int availableToScroll = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
if (availableToScroll > 0) {
deltaY = Math.min(availableToScroll, deltaY);
} else {
deltaY = 0;
}
}
if (deltaY != 0 || deltaX != 0)
scrollBy(deltaX, deltaY);
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
//velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialXVelocity = (int) velocityTracker.getXVelocity();
int initialYVelocity = (int) velocityTracker.getYVelocity();
if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) && getChildCount() > 0) {
fling(-initialXVelocity, -initialYVelocity);
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
return true;
}
/**
* <p>Handles scrolling in response to a "home/end" shortcut press.</p>
*
* @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
* to go the top of the view or
* {@link android.view.View#FOCUS_DOWN} to go the bottom
* @return true if the key event is consumed by this method, false otherwise
*/
public boolean fullScroll(int direction_vert, int direction_horz) {
int scrollAmountY = 0, scrollAmountX = 0;
// vertical
switch (direction_vert) {
case View.FOCUS_UP:
scrollAmountY = -getScrollY();
break;
case View.FOCUS_DOWN:
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
scrollAmountY = (view.getBottom() - getHeight()) - getScrollY();
}
break;
}
// horizontal
switch (direction_horz) {
case View.FOCUS_LEFT:
scrollAmountX = -getScrollX();
break;
case View.FOCUS_RIGHT:
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
scrollAmountX = (view.getRight() - getWidth()) - getScrollX();
}
break;
}
boolean handled = (scrollAmountX != 0) || (scrollAmountY != 0);
if (handled)
doScroll(scrollAmountX, scrollAmountY);
return handled;
}
/**
* Smooth scroll by a Y delta
*
* @param delta the number of pixels to scroll by on the Y axis
*/
private void doScroll(int deltaX, int deltaY) {
if (deltaX != 0 || deltaY != 0) {
smoothScrollBy(deltaX, deltaY);
}
}
/**
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param dx the number of pixels to scroll by on the X axis
* @param dy the number of pixels to scroll by on the Y axis
*/
public final void smoothScrollBy(int dx, int dy) {
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy);
//awakenScrollBars(mScroller.getDuration());
invalidate();
} else {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
scrollBy(dx, dy);
}
mLastScroll = AnimationUtils.currentAnimationTimeMillis();
}
/**
* Like {@link #scrollTo}, but scroll smoothly instead of immediately.
*
* @param x the position where to scroll on the X axis
* @param y the position where to scroll on the Y axis
*/
public final void smoothScrollTo(int x, int y) {
smoothScrollBy(x - getScrollX(), y - getScrollY());
}
/**
* <p>The scroll range of a scroll view is the overall height of all of its
* children.</p>
*/
@Override
protected int computeVerticalScrollRange() {
int count = getChildCount();
return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
}
@Override
protected int computeHorizontalScrollRange() {
int count = getChildCount();
return count == 0 ? getWidth() : (getChildAt(0)).getRight();
}
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
// This is called at drawing time by ViewGroup. We don't want to
// re-show the scrollbars at this point, which scrollTo will do,
// so we replicate most of scrollTo here.
//
// It's a little odd to call onScrollChanged from inside the drawing.
//
// It is, except when you remember that computeScroll() is used to
// animate scrolling. So unless we want to defer the onScrollChanged()
// until the end of the animated scrolling, we don't really have a
// choice here.
//
// I agree. The alternative, which I think would be worse, is to post
// something and tell the subclasses later. This is bad because there
// will be a window where mScrollX/Y is different from what the app
// thinks it is.
//
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollTo(clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()),
clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()));
} else {
scrollTo(x, y);
}
if (oldX != getScrollX() || oldY != getScrollY()) {
onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
}
// Keep on drawing until the animation has finished.
postInvalidate();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// Calling this with the present values causes it to re-clam them
scrollTo(getScrollX(), getScrollY());
}
/**
* Fling the scroll view
*
* @param velocityY The initial velocity in the Y direction. Positive
* numbers mean that the finger/curor is moving down the screen,
* which means we want to scroll towards the top.
*/
public void fling(int velocityX, int velocityY) {
if (getChildCount() > 0) {
int height = getHeight() - getPaddingBottom() - getPaddingTop();
int bottom = getChildAt(0).getHeight();
int width = getWidth() - getPaddingRight() - getPaddingLeft();
int right = getChildAt(0).getWidth();
mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0, bottom - height);
//awakenScrollBars(mScroller.getDuration());
invalidate();
}
}
/**
* {@inheritDoc}
*
* <p>This version also clamps the scrolling to the bounds of our child.
*/
public void scrollTo(int x, int y) {
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
View child = getChildAt(0);
x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
if (x != getScrollX() || y != getScrollY()) {
super.scrollTo(x, y);
}
}
}
private int clamp(int n, int my, int child) {
if (my >= child || n < 0) {
/* my >= child is this case:
* |--------------- me ---------------|
* |------ child ------|
* or
* |--------------- me ---------------|
* |------ child ------|
* or
* |--------------- me ---------------|
* |------ child ------|
*
* n < 0 is this case:
* |------ me ------|
* |-------- child --------|
* |-- mScrollX --|
*/
return 0;
}
if ((my+n) > child) {
/* this case:
* |------ me ------|
* |------ child ------|
* |-- mScrollX --|
*/
return child-my;
}
return n;
}
}
I also get some weird errors when b4a tries to parse the XML file with the library about duplicate definitions. I take out the duplicates, and then it seems to parse ok. Any ideas what I'm doing wrong?