I am a newbie in Android and Robolectric.
I am trying to simulate (as the topic indicates it) a drag event on a viewpager.
I have ever implemented this code and it runs on the emulator.
The matter is mostly: how can I unit test it?
Here is the unit test:
/** imports are skipped **/
#RunWith(RobolectricTestRunner.class)
#Config(manifest = "/src/main/AndroidManifest.xml")
public class TestImages {
private ImageActivity activity;
#InjectResource(R.drawable.ic_launcher)
private static Drawable image;
#Spy
private final PicturesModel picturesModel = spy(new PicturesModelImpl());
/**
* Last touched view
*/
private View lastTouchView;
/**
* Last touched X coordinate
*/
private float lastTouchX;
/**
* Last touched Y coordinate
*/
private float lastTouchY;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final Module roboGuiceModule = RoboGuice.newDefaultRoboModule(Robolectric.application);
final Module pictureModule = Modules.override(roboGuiceModule).with(new PictureModule());
final Module testModule = Modules.override(pictureModule).with(new TestPictureModule(picturesModel));
RoboGuice.setBaseApplicationInjector(Robolectric.application, RoboGuice.DEFAULT_STAGE, testModule);
RoboInjector injector = RoboGuice.getInjector(Robolectric.application);
injector.injectMembersWithoutViews(this);
}
#Test
public void scroll_image() {
final Intent intent = new Intent(Robolectric.getShadowApplication().getApplicationContext(), ImageActivity.class);
final Album album = new Album(1, "album_1", image);
intent.putExtra("album", album);
intent.putExtra("position", 2);
intent.putExtra("imageId", 2L);
// Get the activity
activity = Robolectric.buildActivity(ImageActivity.class).withIntent(intent).create().get();
final Point point = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(point);
// Get a spy viewer otherwise viewPager's width is always 0
final ViewPager viewPager = spy((ViewPager) activity.findViewById(R.id.viewPager));
viewPager.setVisibility(View.VISIBLE);
when(viewPager.getWidth()).thenReturn(point.x - 50);
// First item sent by viewPager before swipe
final int firstItem = viewPager.getCurrentItem();
// Swipe
drag(viewPager, 10F, 10F, point.x - 60F, 10F);
// Next item after swipe
final int secondItem = viewPager.getCurrentItem();
// Comparison
assertThat(firstItem).isEqualTo(2);
assertThat(secondItem).isEqualTo(3);
}
public void touchDown(View view, float x, float y) {
lastTouchX = x;
lastTouchY = y;
lastTouchView = view;
sendMotionEvent(view, MotionEvent.ACTION_DOWN, x, y);
}
public void touchMove(float x, float y) {
lastTouchX = x;
lastTouchY = y;
sendMotionEvent(lastTouchView, MotionEvent.ACTION_MOVE, x, y);
}
public void touchUp() {
sendMotionEvent(
lastTouchView, MotionEvent.ACTION_UP, lastTouchX, lastTouchY);
lastTouchView = null;
}
public void drag(View view, float xStart, float yStart,
float xEnd, float yEnd) {
touchDown(view, xStart, yStart);
touchMove(xEnd, yEnd);
touchUp();
}
private void sendMotionEvent(View view, int action, float x, float y) {
int[] screenOffset = new int[2];
view.getLocationOnScreen(screenOffset);
MotionEvent event = MotionEvent.obtain(100, 200, action,
x + screenOffset[0], y + screenOffset[1], 0);
shadowOf(event).setPointerIds(1, 2);
shadowOf(event).setPointerIndex(1);
view.onTouchEvent(event);
view.dispatchTouchEvent(event);
}
}
and the the activity:
public class ImageActivity extends RoboActivity {
#Inject
private PicturesModel picturesModel;
#InjectView(R.id.viewPager)
private ViewPager viewPager;
private PagerAdapter pagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image);
final Bundle bundle = getIntent().getExtras();
if (bundle != null && !bundle.isEmpty()) {
final long id = (long) bundle.get("imageId");
final int position = (int) bundle.get("position");
final Picture picture = picturesModel.getPicture(id);
if (picture != null) {
pagerAdapter = new ImageAdapter(this, picturesModel.getAllPictures((Album) bundle.get("album")));
viewPager.setAdapter(pagerAdapter);
viewPager.setCurrentItem(position, true);
}
}
}
}
and this is the layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/viewPager" />
Does someone has an idea how to proceed or to tell me where is my mistake?
I think you what you are looking for is the Espresso library.
It simulates UI Events and let you check the state of the view once you threw the event and it is really easy to use. Just don't forget to switch animations off in the developers settings.
Related
I am creating an Android Live Wallpaper with Libgdx , It is just a circle on the screen which gets a random color and position and it slowly moves up .
The issue is after setting the live wallpaper , when I again click on the app icon , instead of a new circle with new color and position , the one that is already running is displayed .
The only way to have a new circle is to stop the live wallpaper by having a different one and then again starting the app.
what I am trying to have is to , each time I click on the icon a new live wallpaper is loaded without affecting the one already running and if i click the "set as live wallpaper" button the old one is replaced with the new one.
Android Launcher Class
public class AndroidLauncher extends AndroidApplication {
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
initialize(new CoreLWP(), config);
}
}
Live Wallpaper class
public class LiveWallpaper extends AndroidLiveWallpaperService {
public CoreLWP wallpaper;
#Override
public void onCreateApplication () {
super.onCreateApplication();
final AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
this.wallpaper = new CoreLWP();
initialize(this.wallpaper, config);
}
}
Core Live Wallpaper class
public class CoreLWP extends ApplicationAdapter implements ApplicationListener, AndroidWallpaperListener {
private ShapeRenderer ball;
private float x,y;
private Random random;
private float r,g,b;
#Override
public void create () {
ball = new ShapeRenderer();
random = new Random();
//generating random position
x = random.nextInt(Gdx.graphics.getWidth());
y = random.nextInt(Gdx.graphics.getHeight());
//generating random rgb values
r = getFloatRandomNum(0, 1) ;
g = getFloatRandomNum(0, 1) ;
b = getFloatRandomNum(0, 1) ;
}
#Override
public void render () {
//setting the background
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//updating x and y position
x = x + 0.5f;
y = y + 0.6f;
ball.begin(ShapeRenderer.ShapeType.Filled);
ball.setColor(r,g,b,1);
ball.circle(x,y,100);
ball.end();
}
//returns a random float number between a range
private float getFloatRandomNum(float min, float max) {
float random = min + new Random().nextFloat() * (max - min);
return random;
}
#Override
public void dispose () {
ball.dispose();
}
#Override
public void offsetChange(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) {}
#Override
public void previewStateChange(boolean isPreview) {}
#Override
public void iconDropped(int x, int y) {}
}
I'm using "subsampling scale image view" in my app and I would like to dynamically add marker pins to a PinView when the user does a long click on it. The marker pin should appear at the clicked position.
I achieved that a marker pin appears after a long click, but at a wrong position. Here is the onCreateView method of my Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
pinCounter=0;
View rootView = inflater.inflate(R.layout.fragment_edit_plan, container, false);
src = getArguments().getString("src");
imageView = (PinView)rootView.findViewById(R.id.imageView);
imageView.setImage(ImageSource.uri(src));
imageView.isLongClickable();
imageView.isClickable();
imageView.hasOnClickListeners();
MapPins = new ArrayList();
imageView.setPins(MapPins);
imageView.setOnLongClickListener(this);
imageView.setOnTouchListener(this);
return rootView;
}
The Listener-Methods:
#Override
public boolean onLongClick(View view) {
if (view.getId() == R.id.imageView) {
Toast.makeText(getActivity(), "Long clicked "+lastKnownX+" "+lastKnownY, Toast.LENGTH_SHORT).show();
Log.d("EditPlanFragment","Scale "+imageView.getScale());
MapPins.add(new MapPin(lastKnownX,lastKnownY,pinCounter));
imageView.setPins(MapPins);
imageView.post(new Runnable(){
public void run(){
imageView.getRootView().postInvalidate();
}
});
return true;
}
return false;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId()== R.id.imageView && event.getAction() == MotionEvent.ACTION_DOWN){
lastKnownX= event.getX();
lastKnownY= event.getY();
}
return false;
}
My XML-File:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="50dp"
>
<com.example.viktoriabock.phoenixversion1.PinView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:longClickable="true"
/>
And the PinView-Class:
public class PinView extends SubsamplingScaleImageView {
private PointF sPin;
ArrayList<MapPin> mapPins;
ArrayList<DrawPin> drawnPins;
Context context;
String tag = getClass().getSimpleName();
public PinView(Context context) {
this(context, null);
this.context = context;
}
public PinView(Context context, AttributeSet attr) {
super(context, attr);
this.context = context;
initialise();
}
public void setPins(ArrayList<MapPin> mapPins) {
this.mapPins = mapPins;
initialise();
invalidate();
}
public void setPin(PointF pin) {
this.sPin = pin;
}
public PointF getPin() {
return sPin;
}
private void initialise() {
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Don't draw pin before image is ready so it doesn't move around during setup.
if (!isReady()) {
return;
}
drawnPins = new ArrayList<>();
Paint paint = new Paint();
paint.setAntiAlias(true);
float density = getResources().getDisplayMetrics().densityDpi;
for (int i = 0; i < mapPins.size(); i++) {
MapPin mPin = mapPins.get(i);
//Bitmap bmpPin = Utils.getBitmapFromAsset(context, mPin.getPinImgSrc());
Bitmap bmpPin = BitmapFactory.decodeResource(this.getResources(), R.drawable.pushpin_blue);
float w = (density / 420f) * bmpPin.getWidth();
float h = (density / 420f) * bmpPin.getHeight();
bmpPin = Bitmap.createScaledBitmap(bmpPin, (int) w, (int) h, true);
PointF vPin = sourceToViewCoord(mPin.getPoint());
//in my case value of point are at center point of pin image, so we need to adjust it here
float vX = vPin.x - (bmpPin.getWidth() / 2);
float vY = vPin.y - bmpPin.getHeight();
canvas.drawBitmap(bmpPin, vX, vY, paint);
//add added pin to an Array list to get touched pin
DrawPin dPin = new DrawPin();
dPin.setStartX(mPin.getX() - w / 2);
dPin.setEndX(mPin.getX() + w / 2);
dPin.setStartY(mPin.getY() - h / 2);
dPin.setEndY(mPin.getY() + h / 2);
dPin.setId(mPin.getId());
drawnPins.add(dPin);
}
}
public int getPinIdByPoint(PointF point) {
for (int i = drawnPins.size() - 1; i >= 0; i--) {
DrawPin dPin = drawnPins.get(i);
if (point.x >= dPin.getStartX() && point.x <= dPin.getEndX()) {
if (point.y >= dPin.getStartY() && point.y <= dPin.getEndY()) {
return dPin.getId();
}
}
}
return -1; //negative no means no pin selected
}
}
You're collecting the screen coordinates of the long press, then in the custom view you're converting them from source coordinates to screen coordinates. You need to convert the event coordinates into source coordinates when you get the tap event, using the view's viewToSourceCoord method.
There's an easier way to do this than using your lastKnown values - use your own GestureDetector.
For a full working example of a long press gesture detector, see the AdvancedEventHandlingActivity sample class.
I want to connect several classes which are functioning separately but are related.
Lets say I am writing an app in which you can swipe to draw a chart. There are lots of classes in the app which are related and should be connected.
For example three of the classes are:
Swiper - responsible for interpreting the gesture of the user
Points - responsible for handling the points on the chart
ChartDrawer - responsible for drawing the chart on the screen
I want to know is there any design pattern such as a connector which can handle the relation and communication of these classes? Any way i can redesign in a better way or make mind more object oriented?
This is my ChartDraw class which extends a view:
public class ChartDraw extends View implements GestureReceiver {
int chartYPosition;
private int circleColor;
private int circleRadius;
int height;
private float lastPointOnChart;
private int lineColor;
private int lineWidth;
private Paint paint;
private float tempPoint;
int width;
public ChartDraw(Context context) {
super(context);
init();
}
public ChartDraw(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ChartDraw(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
this.lineWidth = 15;
this.circleRadius = 20;
this.lineColor = Color.parseColor("#1976D2");
this.circleColor = Color.parseColor("#536DFE");
this.lastPointOnChart = 0.0f;
this.tempPoint = 0.0f;
this.paint = new Paint();
this.height = getHeight();
this.width = getWidth();
this.chartYPosition = this.height / 2;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.chartYPosition = canvas.getHeight() / 2;
this.paint.setStrokeWidth((float) this.lineWidth);
this.paint.setColor(this.lineColor);
canvas.drawLine(0.0f, (float) this.chartYPosition, this.tempPoint, (float) this.chartYPosition, this.paint);
if (this.tempPoint > 20.0f) {
this.paint.setColor(this.circleColor);
canvas.drawCircle(20.0f, (float) this.chartYPosition, 20.0f, this.paint);
drawTriangle(canvas, this.paint, this.tempPoint, this.chartYPosition);
}
}
private void drawTriangle(Canvas canvas, Paint paint, float startX, int startY) {
Path path = new Path();
path.moveTo(startX, (float) (startY - 20));
path.lineTo(startX, (float) (startY + 20));
path.lineTo(30.0f + startX, (float) startY);
path.lineTo(startX, (float) (startY - 20));
path.close();
canvas.drawPath(path, paint);
}
public void onMoveHorizontal(float dx) {
this.tempPoint = this.lastPointOnChart + dx;
invalidate();
}
public void onMoveVertical(float dy) {
}
public void onMovementStop() {
this.lastPointOnChart = this.tempPoint;
}
}
And this is My SwipeManager which is handling user gesture:
public class SwipeManager implements View.OnTouchListener {
GestureReceiver receiver;
private int activePointer;
private float initX,
initY;
private long startTime,
stopTime;
private boolean resolving = false;
private boolean resolved = false;
private Direction direction;
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (receiver == null) throw new AssertionError("You must register a receiver");
switch (motionEvent.getActionMasked()) {
case ACTION_DOWN:
activePointer = motionEvent.getPointerId(0);
initX = motionEvent.getX(activePointer);
initY = motionEvent.getY(activePointer);
startTime = new Date().getTime();
break;
case ACTION_MOVE:
if (!resolving && !resolved) {
resolving = true;
float x = motionEvent.getX(activePointer);
float y = motionEvent.getY(activePointer);
direction = resolveDirection(x, y);
if (direction != Direction.STILL) {
resolved = true;
resolving = false;
} else {
resolving = false;
resolved = false;
}
break;
}
if (resolved) {
if (direction == Direction.HORIZONTAL)
receiver.onMoveHorizontal(motionEvent.getX(activePointer) - initX);
else receiver.onMoveVertical(motionEvent.getX(activePointer) - initY);
}
break;
case ACTION_UP:
resolved = false;
receiver.onMovementStop();
break;
}
return true;
}
private Direction resolveDirection(float x, float y) {
float dx = x - initX;
float dy = y - initY;
float absDx = Math.abs(dx);
float absDy = Math.abs(dy);
if (absDx > absDy + 10) {
return Direction.HORIZONTAL;
} else if (absDy > absDx + 10) {
return Direction.VERTICAL;
}
return Direction.STILL;
}
public void setReceiver(GestureReceiver receiver) {
this.receiver = receiver;
}
private enum Direction {HORIZONTAL, VERTICAL, STILL;}
}
And i didn't start the Points class because i was not sure about the architecture.
I want this Connector to register all the listeners for the classes and wait for a change and inform the corresponding class of the change, like new point added or swipe started and finished or any other event in the app.
Chain of Responsibility might be what you are looking for.
It is a pattern to tie a series of 'processing objects' in a 'chain' that can handle 'command objects'.
I could see you making command objects that encapsulate the touch events and then get passed through several processors and finally get 'processed' by the 'processing objects' which handle input detection/output generation for that particular 'command object'.
I don't know if this is -ideal-, but it is potentially valid.
Other related patterns to look into might be:
https://en.wikipedia.org/wiki/Command_pattern
https://en.wikipedia.org/wiki/Observer_pattern
https://en.wikipedia.org/wiki/Bridge_pattern
Really what you're looking for here is an MVC-style architecture. Your application should (broadly speaking) be separated into 3 different areas:
the Model, which is completely divorced from your presentation or communication concerns. It provides an API for interaction and can be tested entirely independently with a simple framework such as JUnit.
the View, which is responsible for displaying the Model. It may be that a model can be displayed in different ways - in which case you get a single model and a few different views.
the Controller, which is responsible for making changes to the Model in response to user (or other) input.
The important thing is that the three sets of components are loosely-coupled and that responsibilities are clearly separated. All three should communicated via well defined interfaces (perhaps using the Observer, Command and ChainOfResponsibility patterns). In particular, the Model classes should have no direct knowledge of any of the View or Controller classes.
So, you might have some Model/View classes like this...
public interface ChartListener {
void notifyUpdate();
}
public interface Chart {
void newPoint(Point p);
Collection<Point> thePoints();
void addListener(ChartListener listener);
}
public class ChartModel implements Chart {
private final Collection<Point> points;
private final Collection<ChartListener> listeners;
public Collection<Point> thePoints() {
return Collections.unmodifiableCollection(points);
}
public void newPoint(Point p) {
thePoints.add(p);
listeners.stream().forEach(ChartListener::notifyUpdate);
}
public void addListener(ChartListener cl) {
listeners.append(cl);
}
}
public PieChartViewer implements ChartListener {
// All you colour management or appearance-related concerns is in this class.
private final Chart chart;
public PieChartView(Chart chart) {
this.chart = chart;
// set up all the visuals...
}
public void notifyUpdate() {
for (final Point p:chart.thePoints()) {
// draw a point somehow, lines, dots, etc,
}
}
}
Then you might have multiple different implementations of your View classes, utilising the ChartListener interface.
Your Swipe class seems like a Controller class, which would take a ChartModel implementation and then modify it in response to some input from the user.
i am using andengine for implementing accelerometer sensor in android. my requirement is i have to move the ball to the center of the screen using accelerometer. I am able to achieve that when the device is in lying flat position. because it uses the X and Y acceleration values for the calculation in AndEngine library.
but the problem arise when i hold the device in straight up position. the object doesn't move to the center and it always lays down to the bottom bar of the screen. however, i tried to calculate the z value at this time and inject my logic into AccelerationData class to use z value instead of y. but no success. please go through the following code and guide me what wrong i am doing.
AccelerationData.java
public class AccelerationData extends BaseSensorData {
// ===========================================================
// Constants
// ===========================================================
private static final IAxisSwap AXISSWAPS[] = new IAxisSwap[4];
static {
AXISSWAPS[Surface.ROTATION_0] = new IAxisSwap() {
#Override
public void swapAxis(final float[] pValues) {
final float x = -pValues[SensorManager.DATA_X];
final float y;
if(SensorManager.DATA_Z > -0.50 && SensorManager.DATA_Z <0.50){
y = pValues[SensorManager.DATA_Z];
}else{
y = pValues[SensorManager.DATA_Y];
}
pValues[SensorManager.DATA_X] = x;
pValues[SensorManager.DATA_Y] = y;
}
};
AXISSWAPS[Surface.ROTATION_90] = new IAxisSwap() {
#Override
public void swapAxis(final float[] pValues) {
final float x = pValues[SensorManager.DATA_Y];
final float y = pValues[SensorManager.DATA_X];
pValues[SensorManager.DATA_X] = x;
pValues[SensorManager.DATA_Y] = y;
}
};
AXISSWAPS[Surface.ROTATION_180] = new IAxisSwap() {
#Override
public void swapAxis(final float[] pValues) {
final float x = pValues[SensorManager.DATA_X];
final float y = -pValues[SensorManager.DATA_Y];
pValues[SensorManager.DATA_X] = x;
pValues[SensorManager.DATA_Y] = y;
}
};
AXISSWAPS[Surface.ROTATION_270] = new IAxisSwap() {
#Override
public void swapAxis(final float[] pValues) {
final float x = -pValues[SensorManager.DATA_Y];
final float y = -pValues[SensorManager.DATA_X];
pValues[SensorManager.DATA_X] = x;
pValues[SensorManager.DATA_Y] = y;
}
};
}
...
...
}
here i am showing the usage of z value only when device is at 0 degree. any help would be greatly appreciated.
Hi I am using the Gallery widget to show images downloaded from the internet.
to show several images and I would like to have a gradual zoom while people slide up and down on the screen. I know how to implement the touch event the only thing I don't know how to make the whole gallery view grow gradually. I don't want to zoom in on one image I want the whole gallery to zoom in/out gradually.
EDIT3: I manage to zoom the visible part of the gallery but the problem is I need to find a way for the gallery to find out about it and update it's other children too.
What happens is if 3 images are visible then you start zooming and the gallery does get smaller, so do the images but what I would like in this case is more images to be visible but I don't know how to reach this desired effect. Here's the entire code:
public class Gallery1 extends Activity implements OnTouchListener {
private static final String TAG = "GalleryTest";
private float zoom=0.0f;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
Gallery g;
LinearLayout layout2;
private ImageAdapter ad;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery_1);
layout2=(LinearLayout) findViewById(R.id.layout2);
// Reference the Gallery view
g = (Gallery) findViewById(R.id.gallery);
// Set the adapter to our custom adapter (below)
ad=new ImageAdapter(this);
g.setAdapter(ad);
layout2.setOnTouchListener(this);
}
public void zoomList(boolean increase) {
Log.i(TAG, "startig animation");
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(g, "scaleX", zoom),
ObjectAnimator.ofFloat(g, "scaleY", zoom)
);
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) {
// TODO Auto-generated method stub
}
});
set.setDuration(100).start();
}
public class ImageAdapter extends BaseAdapter {
private static final int ITEM_WIDTH = 136;
private static final int ITEM_HEIGHT = 88;
private final int mGalleryItemBackground;
private final Context mContext;
private final Integer[] mImageIds = {
R.drawable.gallery_photo_1,
R.drawable.gallery_photo_2,
R.drawable.gallery_photo_3,
R.drawable.gallery_photo_4,
R.drawable.gallery_photo_5,
R.drawable.gallery_photo_6,
R.drawable.gallery_photo_7,
R.drawable.gallery_photo_8
};
private final float mDensity;
public ImageAdapter(Context c) {
mContext = c;
// See res/values/attrs.xml for the <declare-styleable> that defines
// Gallery1.
TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
mGalleryItemBackground = a.getResourceId(
R.styleable.Gallery1_android_galleryItemBackground, 1);
a.recycle();
mDensity = c.getResources().getDisplayMetrics().density;
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
convertView = new ImageView(mContext);
imageView = (ImageView) convertView;
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setLayoutParams(new Gallery.LayoutParams(
(int) (ITEM_WIDTH * mDensity + 0.5f),
(int) (ITEM_HEIGHT * mDensity + 0.5f)));
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mImageIds[position]);
return imageView;
}
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE
&& event.getPointerCount() > 1) {
midPoint(mid, event);
if(mid.y > start.y){
Log.i(TAG, "Going down (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); // going down so increase
if ((Math.abs(mid.y - start.y) > 10) && (zoom<2.5f)){
zoom=zoom+0.1f;
midPoint(start, event);
zoomList(true);
}
return true;
}else if(mid.y < start.y){
Log.i(TAG, "Going up (Math.abs(mid.y - start.y)= "+(Math.abs(mid.y - start.y))+" and zoom="+zoom); //smaller
if ((Math.abs(mid.y - start.y) > 10) &&(zoom>0.1)){
midPoint(start, event);
zoom=zoom-0.1f;
zoomList(false);
}
return true;
}
}
else if (event.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
Log.e(TAG, "Pointer went down: " + event.getPointerCount());
return true;
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
Log.i(TAG, "Pointer going up");
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "Pointer going down");
start.set(event.getX(), event.getY());
return true;
}
return false;
// indicate event was handled or not
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
I realise I will probably have to extend the Gallery or even another View group or create my own class but I don't know where to start: which method use the one responsible for scaling...
EDIT4: I don't know if he question is clear enough. Here is an example of states:
State one: initial state, we have 3 images in view
State 2: we detect vertical touches going up with 2 fingers = we have to zoom out
state 3: we start zooming = animation on the gallery or on the children???
state 4: gallery detects that it's 3 children are smaller
state 5: gallery adds 1 /more children according to the new available space
LAST UPDATE:
Thanks to all that have posted but I have finally reached a conclusion and that is to not use Gallery at all:
1. It's deprecated
2. It's not customizable enough for my case
If you want to animate several images at once you may want to consider using OpenGl, I am using libgdx library:
https://github.com/libgdx/libgdx
The following ScalingGallery implementation might be of help.
This gallery subclass overrides the getChildStaticTransformation(View child, Transformation t) method in which the scaling is performed. You can further customize the scaling parameters to fit your own needs.
Please note the ScalingGalleryItemLayout.java class. This is necessary because after you have performed the scaling operationg on the child views, their hit boxes are no longer valid so they must be updated from with the getChildStaticTransformation(View child, Transformation t) method.
This is done by wrapping each gallery item in a ScalingGalleryItemLayout which extends a LinearLayout. Again, you can customize this to fit your own needs if a LinearLayout does not meet your needs for layout out your gallery items.
File : /src/com/example/ScalingGallery.java
/**
* A Customized Gallery component which alters the size and position of its items based on their position in the Gallery.
*/
public class ScalingGallery extends Gallery {
public static final int ITEM_SPACING = -20;
private static final float SIZE_SCALE_MULTIPLIER = 0.25f;
private static final float ALPHA_SCALE_MULTIPLIER = 0.5f;
private static final float X_OFFSET = 20.0f;
/**
* Implemented by child view to adjust the boundaries after it has been matrix transformed.
*/
public interface SetHitRectInterface {
public void setHitRect(RectF newRect);
}
/**
* #param context
* Context that this Gallery will be used in.
* #param attrs
* Attributes for this Gallery (via either xml or in-code)
*/
public ScalingGallery(Context context, AttributeSet attrs) {
super(context, attrs);
setStaticTransformationsEnabled(true);
setChildrenDrawingOrderEnabled(true);
}
/**
* {#inheritDoc}
*
* #see #setStaticTransformationsEnabled(boolean)
*
* This is where the scaling happens.
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
child.invalidate();
t.clear();
t.setTransformationType(Transformation.TYPE_BOTH);
// Position of the child in the Gallery (... +2 +1 0 -1 -2 ... 0 being the middle)
final int childPosition = getSelectedItemPosition() - getPositionForView(child);
final int childPositionAbs = (int) Math.abs(childPosition);
final float left = child.getLeft();
final float top = child.getTop();
final float right = child.getRight();
final float bottom = child.getBottom();
Matrix matrix = t.getMatrix();
RectF modifiedHitBox = new RectF();
// Change alpha, scale and translate non-middle child views.
if (childPosition != 0) {
final int height = child.getMeasuredHeight();
final int width = child.getMeasuredWidth();
// Scale the size.
float scaledSize = 1.0f - (childPositionAbs * SIZE_SCALE_MULTIPLIER);
if (scaledSize < 0) {
scaledSize = 0;
}
matrix.setScale(scaledSize, scaledSize);
float moveX = 0;
float moveY = 0;
// Moving from right to left -- linear move since the scaling is done with respect to top-left corner of the view.
if (childPosition < 0) {
moveX = ((childPositionAbs - 1) * SIZE_SCALE_MULTIPLIER * width) + X_OFFSET;
moveX *= -1;
} else { // Moving from left to right -- sum of the previous positions' x displacements.
// X(n) = X(0) + X(1) + X(2) + ... + X(n-1)
for (int i = childPositionAbs; i > 0; i--) {
moveX += (i * SIZE_SCALE_MULTIPLIER * width);
}
moveX += X_OFFSET;
}
// Moving down y-axis is linear.
moveY = ((childPositionAbs * SIZE_SCALE_MULTIPLIER * height) / 2);
matrix.postTranslate(moveX, moveY);
// Scale alpha value.
final float alpha = (1.0f / childPositionAbs) * ALPHA_SCALE_MULTIPLIER;
t.setAlpha(alpha);
// Calculate new hit box. Since we moved the child, the hitbox is no longer lined up with the new child position.
final float newLeft = left + moveX;
final float newTop = top + moveY;
final float newRight = newLeft + (width * scaledSize);
final float newBottom = newTop + (height * scaledSize);
modifiedHitBox = new RectF(newLeft, newTop, newRight, newBottom);
} else {
modifiedHitBox = new RectF(left, top, right, bottom);
}
// update child hit box so you can tap within the child's boundary
((SetHitRectInterface) child).setHitRect(modifiedHitBox);
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Helps to smooth out jittering during scrolling.
// read more - http://www.unwesen.de/2011/04/17/android-jittery-scrolling-gallery/
final int viewsOnScreen = getLastVisiblePosition() - getFirstVisiblePosition();
if (viewsOnScreen <= 0) {
super.onLayout(changed, l, t, r, b);
}
}
private int mLastDrawnPosition;
#Override
protected int getChildDrawingOrder(int childCount, int i) {
//Reset the last position variable every time we are starting a new drawing loop
if (i == 0) {
mLastDrawnPosition = 0;
}
final int centerPosition = getSelectedItemPosition() - getFirstVisiblePosition();
if (i == childCount - 1) {
return centerPosition;
} else if (i >= centerPosition) {
mLastDrawnPosition++;
return childCount - mLastDrawnPosition;
} else {
return i;
}
}
}
File : /src/com/example/ScalingGalleryItemLayout.java
public class ScalingGalleryItemLayout extends LinearLayout implements SetHitRectInterface {
public ScalingGalleryItemLayout(Context context) {
super(context);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalingGalleryItemLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Rect mTransformedRect;
#Override
public void setHitRect(RectF newRect) {
if (newRect == null) {
return;
}
if (mTransformedRect == null) {
mTransformedRect = new Rect();
}
newRect.round(mTransformedRect);
}
#Override
public void getHitRect(Rect outRect) {
if (mTransformedRect == null) {
super.getHitRect(outRect);
} else {
outRect.set(mTransformedRect);
}
}
}
File : /res/layout/ScaledGalleryItemLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.ScalingGalleryItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gallery_item_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:id="#+id/gallery_item_image"
android:layout_width="360px"
android:layout_height="210px"
android:layout_gravity="center"
android:antialias="true"
android:background="#drawable/gallery_item_button_selector"
android:cropToPadding="true"
android:padding="35dp"
android:scaleType="centerInside" />
<TextView
android:id="#+id/gallery_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#drawable/white"
android:textSize="30sp" />
</com.example.ScalingGalleryItemLayout>
To keep the state of the animation after it is done, just do this on your animation:
youranim.setFillAfter(true);
Edit :
In my project, I use this method and i think, it's help you :
http://developer.sonymobile.com/wp/2011/04/12/how-to-take-advantage-of-the-pinch-to-zoom-feature-in-your-xperia%E2%84%A2-10-apps-part-1/
U can do Image Zoom pinch option for gallery also.
by using below code lines:
you can download the example.
https://github.com/alvinsj/android-image-gallery/downloads
I hope this example will help to u..if u have any queries ask me.....
This is solution
integrate gallery component in android with gesture-image library
gesture-imageView
And here is full sample code
SampleCode