When I run the following code it crash and throws an NPE error.
I try to access an object, in this case a textview in a fragment activity. How should I go about it ?
Below is the code I have pasted from the fragment activity.
Another issue is how to use the findViewById in the fragment activity.
public class Report extends Fragment implements SensorEventListener {
private static final double NOISE = 2.0 ;
//define
private SensorManager sensorManager;
private Sensor accelerometer;
private boolean mInitialized;
private static final String TAG = "Report";
private TextView Counter;
double X,Y,Z;
double lastX, lastY, lastZ;
double deltaX, deltaY, deltaZ;
int stepsCount;
private int threshold; // point at which we want to trigger a step.
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.report, container, false);
Counter = getActivity().findViewById(R.id.counter);
threshold = 10;
//initialize the variables
mInitialized = false;
//get the sensor manager
sensorManager = (SensorManager)getActivity().getSystemService(getActivity().SENSOR_SERVICE);
//get accelerometer sensor.
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
startSensor();
return view;
}
private void startSensor() {
//register a listener
sensorManager.registerListener(Report.this, accelerometer ,SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
// Log.d (TAG, "onSensorChanged: X:" + sensorEvent.values[0] + "Y:" + sensorEvent.values[1] + "Z:" + sensorEvent.values[2]);
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
final double alpha = 0.8; // constant for our filter below
double[] gravity = {0, 0, 0};
// Isolate the force of gravity with the low-pass filter.
gravity[0] = alpha * gravity[0] + (1 - alpha) * sensorEvent.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * sensorEvent.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * sensorEvent.values[2];
// Log.d(TAG,"GRAVITY: X:" + gravity[0] + "Y:" + gravity[1] + "Z:" + gravity[2] );
// Remove the gravity contribution with the high-pass filter.
X = sensorEvent.values[0] - gravity[0];
Y = sensorEvent.values[1] - gravity[1];
Z = sensorEvent.values[2] - gravity[2];
//Log.d(TAG,"X:" + X + "Y:" + Y + "Z:" + Z);
lastX = 0;
lastY = 0;
lastZ = 0;
if (!mInitialized) {
//sensor is used for the first time, initialize the last read values
lastX = X;
lastY = Y;
lastZ = Z;
mInitialized = true;
} else {
// sensor is already initialized, and we have previously read values.
// take difference of past and current values and decide which
// axis acceleration was detected by comparing values
deltaX = Math.abs(lastX - X);
deltaY = Math.abs(lastY - Y);
deltaZ = Math.abs(lastZ - Z);
// Log.d(TAG,"X:"+ deltaX + "Y:" + deltaY + "Z:" + deltaZ);
if (deltaX < NOISE)
deltaX = (float) 0.0;
if (deltaY < NOISE)
deltaY = (float) 0.0;
if (deltaZ < NOISE)
deltaZ = (float) 0.0;
lastX = X;
lastY = Y;
lastZ = Z;
if (deltaX > deltaY) {
// Horizontal shake
// do something here if you like
} else if (deltaY > deltaX) {
// Vertical shake
// do something here if you like
} else if (deltaZ>threshold){
stepsCount++;
Log.d(TAG,"STEPS:" + stepsCount);
}
Counter.setText(String.valueOf(stepsCount));//this is the line that throws the error
}
}
#Override
public void onResume(){
super.onResume();
stepsCount=0;
// put your code here...
}
#Override
public void onPause(){
super.onPause();
sensorManager.unregisterListener(this);
// put your code here...
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}
This line in onCreateView():
Counter = getActivity().findViewById(R.id.counter);
needs to be:
Counter = view.findViewById(R.id.counter);
As you're obtaining your TextView from the layout you inflated the line before, not the Activity. As a side note, you shouldn't be naming variables with a capital letter at the front. Only classes should begin with capital letters.
Related
I have an SQLite database that contains x y coordinates of specific points in an image (Aya_No) among other data, I would like to get information from the db regrading these specific points (Aya_No) on LongPress
Here is part of the db:
Here is a sample image:
I have managed to get screen width and height using this code:
public static int getScreenX (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxWidth = displayMetrics.widthPixels;
return maxWidth;
}
public static int getScreenY (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxHight = displayMetrics.heightPixels;
return maxHight;
}
and I have managed to get x y OnTouchListener and pass it to OnLongClickListener using this code:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
x = event.getX();
y = event.getY();
return false;
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
double Talorance = 25;
double screenX = Utils.getScreenX(context);
double screenY = Utils.getScreenY(context);
// Adjusted x
double xPer = x/Utils.getScreenX(context); // .315
double adjXMin = (x - Talorance) * xPer;
double adjXMax = (x + Talorance) * xPer;
// Adjusted y
double yPer = y/Utils.getScreenY(context); //.33
double adjYMin = (y - Talorance) * yPer;
double adjYMax = (y + Talorance) * yPer;
String ScreenXY = String.format("ScreenXY: (%.2f, %.2f)",screenX, screenY);
String ClickedXY = String.format("ClickedXY: (%.2f, %.2f)",x,y);
String AdjustedMin = String.format("AdjustedMinXY: (%.2f, %.2f)",adjXMin,adjYMin);
String AdjustedMax = String.format("AdjustedMaxXY: (%.2f, %.2f)",adjXMax,adjYMax);
Log.w("ScreenXY", ScreenXY);
Log.w("ClickedXY ", ClickedXY);
Log.w("MinXY", AdjustedMin);
Log.w("MaxXY",AdjustedMax);
int page = lastpage - QuranActivity.mCurrentPage;
return true;
}
});
the results of the last code after longPress of Ayah 1 (circled in red in the 1st image) is:
W/ScreenXY: ScreenXY: (1440.00, 2712.00)
W/ClickedXY: ClickedXY: (574.98, 1064.89)
W/MinXY: AdjustedMinXY: (219.60, 408.32)
W/MaxXY: AdjustedMaxXY: (239.57, 427.96)
while the x y of this particular point in the db is 181, 301
after the long details above and bearing in mind that what I am asking for is actually implemented in many apps using the same image and db ..
How may I adjust x y (of different screen sizes and densities) obtained OnTouchListener to match or be close to the ones in the db (with some tolerance) in order to get relevant information from the db?
I have tried using % of total width and height to adjust x y as you saw in my code, but the results varied from screen size to another and were not close to the ones in the db.
PLEASE HELP!
The solution required some analysis:
1. Get screen size
public static int getScreenX (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxWidth = displayMetrics.widthPixels;
return maxWidth;
}
public static int getScreenY (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxHight = displayMetrics.heightPixels;
return maxHight;
}
2. get image xy
image_IV.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
x = event.getX();
y = event.getY();
view.onTouchEvent(event);
return true;
}
});
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
return false;
}
});
3. calculate xy relative to screen size
double ScreenX = Utils.getScreenX(context);
double ScreenY = Utils.getScreenY(context);
double xPer = x / ScreenX;
double yPer = y / ScreenY;
4. do some excel work to estimate screen size basis used in db
5. add screen size results to project
double DBScreenX = 0;
double DBScreenY = 0;
if (LandScape) {
DBScreenX = 465;
DBScreenY = 280;
} else {
DBScreenX = 480;
DBScreenY = 822;
}
6. add some tolerance around 15dp
public static int dpToPx(int dp) {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
double Talorance = Utils.dpToPx(15);
7. calculate adjust xy and determine range using tolerance
// X axis
double xPer = x / ScreenX;
double Adjustedx = xPer * DBScreenX;
double MinX = Adjustedx - Talorance;
double MaxX = Adjustedx + Talorance;
// Y axis
double yPer = y / ScreenY;
double Adjustedy = yPer * DBScreenY;
double MinY = Adjustedy - Talorance;
double MaxY = Adjustedy + Talorance;
8. get info from db based on adjuted xy range
public int getSorahNo (int page, double minX, double maxX, double minY, double maxY) {
int sorahNo = 1000;
open();
Cursor c = mDatabase.rawQuery("SELECT " + Contract.QuranTable.COLUMN_SORAH + " FROM " + Contract.QuranTable.TABLE_NAME +
" WHERE " + Contract.QuranTable.COLUMN_PAGE + " = " + page + " AND " +
Contract.QuranTable.COLUMN_X + " BETWEEN " + minX + " AND " + maxX + " AND " +
Contract.QuranTable.COLUMN_Y + " BETWEEN " + minY + " AND " + maxY, null);
if (c.moveToFirst()) {
do {
sorahNo = c.getInt(c.getColumnIndex(Contract.QuranTable.COLUMN_SORAH));
} while (c.moveToNext());
}
c.close();
if( sorahNo == 1000){
Log.w("getSorahNo ...", "Not successful ... Could not find Sorah based on x y" );
} else {
Log.w("getSorahNo ...", "is successful ... Sorah No = " + sorahNo);
}
close();
return sorahNo;
}
I am developing a joystick button. Everything is ok when moving it around circle, but when I want to move it up or down, it goes only around circle again from the right side. So, I want to have both options (to move up or down in circle like real joystick). Here is my code example. Thanks for any help
public class JoystickView extends FrameLayout implements View.OnTouchListener {
private Context context;
private ImageView backgroundImageView;
private ImageView buttonImageView;
private RelativeLayout relativeLayout;
private View rootView;
float xx = 0;
float yy = 0;
public JoystickView(Context context) {
super(context);
this.context = context;
setLayout(this);
buttonImageView.setOnTouchListener(this);
this.setClipChildren(false);
}
public void setLayout(ViewGroup view) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
View v = inflateControlerLayout();
view.addView(v, layoutParams);
}
protected View inflateControlerLayout() {
initSlider();
rootView = relativeLayout;
return rootView;
}
public void initSlider() {
if (relativeLayout == null) {
relativeLayout = new RelativeLayout(context);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
relativeLayout.setLayoutParams(layoutParams);
relativeLayout.setClipChildren(false);
}
if (backgroundImageView == null) {
backgroundImageView = new ImageView(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
backgroundImageView.setLayoutParams(layoutParams);
// load image
try {
// get input stream
InputStream ims = getContext().getAssets().open("joystick_background.png");
// load image as Drawable
Drawable d = Drawable.createFromStream(ims, null);
// set image to ImageView
backgroundImageView.setImageDrawable(d);
} catch (IOException ex) {
return;
}
}
if (buttonImageView == null) {
buttonImageView = new ImageView(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER);
buttonImageView.setLayoutParams(layoutParams);
// load image
try {
// get input stream
InputStream ims2 = getContext().getAssets().open("jostick_button.png");
// load image as Drawable
Drawable d2 = Drawable.createFromStream(ims2, null);
// set image to ImageView
buttonImageView.setImageDrawable(d2);
buttonImageView.bringToFront();
} catch (IOException ex) {
return;
}
}
if (relativeLayout != null) {
relativeLayout.addView(backgroundImageView);
relativeLayout.addView(buttonImageView);
}
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
view.setX(xx);
view.setY(yy);
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_MOVE:
float cx = view.getWidth() / 2.f;
float cy = view.getHeight() / 2.f;
float x = motionEvent.getRawX();
float y = motionEvent.getRawY();
float w = buttonImageView.getWidth();
float h = buttonImageView.getHeight();
double r = Math.min(cx, cy) / 2.;
double dx = x - cx;
double dy = y - cy;
double hypot = Math.hypot(dx, dy);
double cos = dx / hypot;
double sin = dy / hypot;
double rdx = hypot < 1. ? 0. : r * cos;
double rdy = hypot < 1. ? 0. : r * sin;
buttonImageView.setTranslationX((float) (cx + rdx - w / 2.));
buttonImageView.setTranslationY((float) (cy + rdy - h / 2.));
break;
}
return true;
}
}
These lines constrain the coordinates to a circle (oval) path or center:
double rdx = hypot < 1. ? 0. : r * cos;
double rdy = hypot < 1. ? 0. : r * sin;
For hypot to be less than 1 (that's 1 pixel) you'd have to be dead center.
I changed it to only constrain the coordinates within the circle:
double rcos = r * cos;
double rsin = r * sin;
double rdx = Math.abs(dx) < Math.abs(rcos) ? dx : rcos;
double rdy = Math.abs(dy) < Math.abs(rsin) ? dy : rsin;
I think this might be what you want.
I have found a working solution if someone is looking for it
private Point calculate (float x, float y) {
float cx = buttonImageView.getWidth() / 2.f;
float cy = buttonImageView.getHeight() / 2.f;
double r = cx / 2.; // vrednost radius
double dx = x;
double dy = y;
double hypot = Math.hypot(dx, dy); // izracun hipotenuze
double cos = dx / hypot; // cos
double sin = dy / hypot; // sin
double rcos = r * cos;
double rsin = r * sin;
double rdx = Math.abs(dx) < Math.abs(rcos) ? dx : rcos; // if,else
double rdy = Math.abs(dy) < Math.abs(rsin) ? dy : rsin;
return new Point((int)rdx, (int)rdy);
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
final float x = motionEvent.getRawX(); // x točko
final float y = motionEvent.getRawY(); // y točka
//Log.d("VALUES", "RAW X:" + motionEvent.getRawX() + ", RAW Y:" + motionEvent.getRawY() + ", X:" + motionEvent.getX() + ", CX:" + cx + ", CY:" + cy + ", dx:" + dx + ", dy:" + dy + ", Hypo:" + hypot + ", cos:" + cos + ", sin" + sin);
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
xDelta = view.getX() - x;
yDelta = view.getY() - y;
break;
case MotionEvent.ACTION_UP:
doBounceAnimation(buttonImageView);
doVibration();
view.setX(xx);
view.setY(yy);
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_MOVE:
final float transX = (float) x;
final float transY = (float) y;
thread = new Thread() {
#Override
public void run() {
Point newPoint = calculate(transX+ xDelta,transY + yDelta);
buttonImageView.setX(newPoint.x);
buttonImageView.setY(newPoint.y);
}
};
thread.start();
Log.d(TRANSLATIONX,"X:" + transX + ", Y:" + transY);
break;
}
return true;
}
Enjoy coding!
i have a Problem with my Preview Zoom for the Camera2 API. I am using a TextureView.
I want to zoom only the preview Stream that was showed in the TextureView.
I want to zoom the Area where i use the Zoom Gesture.
I use the SimpleOnScaleGestureListener!
I added following Code. The zoomingFactor and the x and y Position are right.
private void updateTextureViewSize(float xPosi,float yPosi, float scale){
float scaleX = 1.0f;
float scaleY = 1.0f;
float mVideoWidth = mCamcontrol.getmPreviewSize().getWidth();
float mVideoHeight = mCamcontrol.getmPreviewSize().getHeight();
int rotation = getWindowManager().getDefaultDisplay().getRotation();
RectF viewRect = new RectF(0, 0, 1440, 2560);
RectF bufferRect = new RectF(0, 0, mVideoHeight, mVideoWidth);
bufferRect.offset(xPosi - bufferRect.centerX(), yPosi - bufferRect.centerY());
//16:9 faktor
scaleX = ((mScale * scale) / 9f) * 16f;
scaleY = ((mScale * scale) / 16f) * 9f;
Matrix matrix = new Matrix();
matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.FILL);
scalefactorView.setText(String.valueOf(xPosi) + " " + String.valueOf(yPosi));
matrix.setScale(scaleY, scaleX, xPosi, yPosi);
matrix.postRotate(90 * (rotation - 2), xPosi, yPosi);
mTextureView.setTransform(matrix);
}
Zooming is Right, but not the Position where i Zoom. For Example! When i zoom on the position right/middle i see only the left/top rectangle of the Stream.
I added the following pictures to unterstand the problem.
Android Camera2 api : Pinch Zoom In/Out
Use this sample code for Camera2Basic from google developers. https://github.com/googlesamples/android-Camera2Basic
Now declare two class variables –
public float finger_spacing = 0;
public int zoom_level = 1;
and update the given onTouch() method.
public boolean onTouch(View v, MotionEvent event) {
try {
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
float maxzoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM))*10;
Rect m = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
int action = event.getAction();
float current_finger_spacing;
if (event.getPointerCount() > 1) {
// Multi touch logic
current_finger_spacing = getFingerSpacing(event);
if(finger_spacing != 0){
if(current_finger_spacing > finger_spacing && maxzoom > zoom_level){
zoom_level++;
} else if (current_finger_spacing < finger_spacing && zoom_level > 1){
zoom_level--;
}
int minW = (int) (m.width() / maxzoom);
int minH = (int) (m.height() / maxzoom);
int difW = m.width() - minW;
int difH = m.height() - minH;
int cropW = difW /100 *(int)zoom_level;
int cropH = difH /100 *(int)zoom_level;
cropW -= cropW & 3;
cropH -= cropH & 3;
Rect zoom = new Rect(cropW, cropH, m.width() - cropW, m.height() - cropH);
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
finger_spacing = current_finger_spacing;
} else{
if (action == MotionEvent.ACTION_UP) {
//single touch logic
}
}
try {
mCaptureSession
.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException ex) {
ex.printStackTrace();
}
} catch (CameraAccessException e) {
throw new RuntimeException("can not access camera.", e);
}
return true;
}
//Determine the space between the first two fingers
#SuppressWarnings("deprecation")
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
Thanks to #arin 's answer, I made an improved version.
His code is basically working, but there are 2 problems:
1) Readability - actually I don't know what is going on calculating the Rect zoom
2) In my Android 7.1.1 device, the preview will freeze if the zoom is big to a certain extent. Since I solved this problem with the code below, I am pretty sure it is because the original code allowed over-zooming beyond camera's maximum zoom ratio.
(In fact, I don't know why he needs to apply *10 on the ratio returned by CameraCharacteristics)
Below are my codes: (I do this all inside my custom TextureView, which also stores my Camera2 objects and logics):
Related Member variables:
protected CameraCharacteristics cameraCharacteristics;
protected CameraCaptureSession captureSession;
protected CaptureRequest.Builder previewRequestBuilder;
//Zooming
protected float fingerSpacing = 0;
protected float zoomLevel = 1f;
protected float maximumZoomLevel;
protected Rect zoom;
Right after you get CameraCharacteristics from CameraManager, probably in some initial setup:
maximumZoomLevel = cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
override onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
try {
Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (rect == null) return false;
float currentFingerSpacing;
if (event.getPointerCount() == 2) { //Multi touch.
currentFingerSpacing = getFingerSpacing(event);
float delta = 0.05f; //Control this value to control the zooming sensibility
if (fingerSpacing != 0) {
if (currentFingerSpacing > fingerSpacing) { //Don't over zoom-in
if ((maximumZoomLevel - zoomLevel) <= delta) {
delta = maximumZoomLevel - zoomLevel;
}
zoomLevel = zoomLevel + delta;
} else if (currentFingerSpacing < fingerSpacing){ //Don't over zoom-out
if ((zoomLevel - delta) < 1f) {
delta = zoomLevel - 1f;
}
zoomLevel = zoomLevel - delta;
}
float ratio = (float) 1 / zoomLevel; //This ratio is the ratio of cropped Rect to Camera's original(Maximum) Rect
//croppedWidth and croppedHeight are the pixels cropped away, not pixels after cropped
int croppedWidth = rect.width() - Math.round((float)rect.width() * ratio);
int croppedHeight = rect.height() - Math.round((float)rect.height() * ratio);
//Finally, zoom represents the zoomed visible area
zoom = new Rect(croppedWidth/2, croppedHeight/2,
rect.width() - croppedWidth/2, rect.height() - croppedHeight/2);
previewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
fingerSpacing = currentFingerSpacing;
} else { //Single touch point, needs to return true in order to detect one more touch point
return true;
}
captureSession.setRepeatingRequest(previewRequestBuilder.build(), captureCallback, null);
return true;
} catch (final Exception e) {
//Error handling up to you
return true;
}
}
And the getFingerSpacing method:
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
Finally don't forget to set the crop region when you actually take the photo. My code is base on this Camera2Basic, I do this inside the captureStillPicture() method:
//Zoom
if (zoom != null) {
captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
#arin Answer is working thank #arin just one thing zoom sensitivity too high.
To control this i make some changes in might be useful to you.
Change zoom_level data type to double
public int zoom_level = 1; to public double zoom_level = 1;
Then increase or decrease zoom_level with low value i use 0.4
if (current_finger_spacing > finger_spacing && maxzoom > zoom_level) {
zoom_level = zoom_level + .4;
//zoom_level++;
} else if (current_finger_spacing < finger_spacing && zoom_level > 1) {
zoom_level = zoom_level - .4;
//zoom_level--;
}
Here is a Pan and Zoom object from Camera2 that I made to work using the OnScaleGestureListener and SimpleOnGestureListener-onScroll outputs. This will only work as expected if you have a camera with support level > LEGACY, as LEGACY only supports crop to center.
Two caveats:
One is that this is currently NOT set up to output to JPEG output, as rectangles for JPEG outputs must have dimensions which are multiple of 16 (See why here). The second is that I've locked my screen to landscape mode, and my camera is locked to landscape as well, but it should be possible to deal with screen rotations after a few tweaks.
You'll need to pass in the screen dimensions
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) mView.getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
the Maximum Camera Digital Zoom
try {
CameraManager manager = (CameraManager) mView.getContext().getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
float maxZoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM));
} catch (CameraAccessException e) {
e.printStackTrace();
}
the Camera Sensor's Active Array Size
try {
CameraManager manager = (CameraManager) mView.getContext().getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
Rect rectInit = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
} catch (CameraAccessException e) {
e.printStackTrace();
}
Here is my object initialization
mScaler = new CamScaler(maxZoom, rectInit.width(), rectInit.height(), displayMetrics.heightPixels, displayMetrics.widthPixels);
the CamScaler class
public class CamScaler {
private final float ZOOM_MIN = 1.0f;
private final int X_MIN = 0;
private final int Y_MIN = 0;
private int displayWidth;
private int displayHeight;
private Rect current_rect;
private int xCenter;
private int yCenter;
private int xWidth;
private int yHeight;
private int xMax;
private int yMax;
private float zoomMax;
private float zoomCurrent;
public CamScaler(float zoomMax, int xMax, int yMax, int displayHeight, int displayWidth) {
this.xMax = xMax;
this.yMax = yMax;
this.zoomMax = zoomMax;
current_rect = new Rect(X_MIN,Y_MIN, xMax, yMax); //(0,0,xMax,yMax) as the starting rectangle
zoomCurrent = ZOOM_MIN;
xWidth = current_rect.width();
yHeight = current_rect.height();
xCenter = current_rect.centerX();
yCenter = current_rect.centerY();
this.displayHeight = displayHeight;
this.displayWidth = displayWidth;
}
public void pan(float distanceX, float distanceY){
//calculate the shift in the we want to take on the camera sensor with respect to the distance moved on the screen
int xShift = Math.round((distanceX/displayWidth)*xWidth); //scales down to a percentage of the current view width->converts to a pixel shift
int yShift = Math.round((distanceY/displayHeight)*yHeight); //scales down to a percentage of the current view height->converts to a pixel shift
//check if the shift will push us pass our maximums, this should account for both negative and positive values of xShift and yShift correctly
if ( !((xCenter + Math.round(xWidth/2.0) + xShift < xMax) && (xCenter - Math.round(xWidth/2.0) + xShift > 0))) { //if not within xBounds, set xShift to 0
xShift = 0;
}
if ( !((yCenter + Math.round(yHeight/2) + yShift < yMax) && (yCenter - Math.round(yHeight/2.0) + yShift > 0))) { //if not within yBounds, set yShift to 0
yShift = 0;
}
Log.d("Scaler", "pan: xShift" + xShift + " yShift " + yShift);
current_rect.offset(xShift,yShift);
Log.d("Scaler", "pan: current_rect" + current_rect.toString());
xCenter = current_rect.centerX(); //update center
yCenter = current_rect.centerY(); //update center
}
public void zoom(float scale_change){
if ( (zoomCurrent*scale_change < zoomMax) && (zoomCurrent*scale_change > ZOOM_MIN) ){ //if we are within zoom bounds
zoomCurrent *= scale_change; //update the zoom factor
int newWidthHalf = (int)Math.floor(xMax/zoomCurrent/2.0);
int newHeightHalf = (int)Math.floor(yMax/zoomCurrent/2.0);
int xTempCenter = xCenter;
int yTempCenter = yCenter;
//if at edge we need to shift and scale
if (xCenter + newWidthHalf > xMax) { //if at right edge
xTempCenter = xMax - newWidthHalf; //shift center to the left
} else if (xCenter - newWidthHalf < 0) { //if at left edge
xTempCenter = newWidthHalf; //shift center to the right
}
if (yCenter + newHeightHalf > yMax) { //if at bottom
yTempCenter = yMax - newHeightHalf; //shift center up
} else if (yCenter - newHeightHalf < 0) { //if at top
yTempCenter = newHeightHalf; //shift center down
}
Log.d("Scaler", "zoom: " + zoomCurrent);
Log.d(TAG, "current center(x,y) " + xTempCenter + " " + yTempCenter + "current halfwidths(x,y) " + newWidthHalf + " " + newHeightHalf);
current_rect.set(xTempCenter - newWidthHalf, yTempCenter - newHeightHalf,xTempCenter + newWidthHalf, yTempCenter + newHeightHalf);
Log.d("Scaler", "zoom: current_rect" + current_rect.toString());
xWidth = current_rect.width();
yHeight = current_rect.height();
xCenter = current_rect.centerX(); //update center
yCenter = current_rect.centerY(); //update center
} //if not in digital zoom bounds, do nothing
}
public Rect getCurrentView() {
return current_rect;
}
}
And how to use it
public void pan(float distanceX, float distanceY){
if (mScaler != null) {
synchronized (mScaler) {
mScaler.pan(distanceX, distanceY);
try {
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mScaler.getCurrentView());
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
public void zoom(float scale_factor) {
if (mScaler!= null) {
synchronized (mScaler) {
mScaler.zoom(scale_factor);
try {
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mScaler.getCurrentView());
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
The inputs to these functions are directly passed through from the gesture listeners
I hope this helps someone!
In addition to arin answer,Need to add captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
To captureStillPicture() method to let zoom take effect on capture
I got this working class that creates a DarkHole object in the screen. Its a filled circle with random color, position and radius. When screen is touched, it is supposed to draw a ball avoiding the other ones that have already been drawn.
I used this method:
Colision by Radius
public class DarkHole extends View {
public static final int maxDiameter = 250;
public static final int minDiameter = 240;
/**
* This class log tag.
*/
private static final String LOG_TAG = DarkHole.class.getSimpleName();
private static List<ShapeDrawable> mHoles;
private int mWindowWidth;
private int mWindowHeight;
private Random random;
/**
* The constructor;
*
* #param context application context.
*/
public DarkHole(Context context) {
super(context);
mHoles = new ArrayList<ShapeDrawable>();
random = new Random();
//Get Screen Size
Point point = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
display.getSize(point);
// Get screen max x and y.
mWindowWidth = point.x;
mWindowHeight = point.y;
}
/**
* Draw random hole.
*/
private void generateRandomHole() {
while(true) {
ShapeDrawable hole = new ShapeDrawable(new OvalShape());
// Generate random color.
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
int randomColor = Color.rgb(r, g, b);
// Generate random position.
int diameter = random.nextInt(maxDiameter - minDiameter + 1) + minDiameter;
int x = random.nextInt((mWindowWidth - diameter) + 1);
int y = random.nextInt((mWindowHeight - diameter) + 1);
hole.getPaint().setColor(randomColor);
hole.setBounds(x, y, x + diameter, y + diameter);
if (checkDrawContains(hole)) {
Log.d(LOG_TAG, "[generateRandomHole] contains!");
hole = null;
random = null;
} else {
Log.d(LOG_TAG, "[generateRandomHole] not contains!");
mHoles.add(hole);
break;
}
}
}
/**
* Draw informative text.
*
* #param canvas canvas object.
*/
private void generateText(Canvas canvas) {
Paint color = new Paint();
color.setColor(Color.BLACK);
color.setTextSize(70);
color.setAntiAlias(true);
color.setDither(true);
canvas.drawText("Bolas: " + mHoles.size(), 10, 50, color);
}
private boolean checkDrawContains(ShapeDrawable newHole) {
long newCenterX = newHole.getBounds().left + (newHole.getBounds().width()/2);
long newCenterY = newHole.getBounds().top + (newHole.getBounds().height()/2);
Log.d(LOG_TAG, "[checkDrawContains] newCenterX: " + newCenterX);
for(ShapeDrawable hole: mHoles) {
long centerX = hole.getBounds().left + (hole.getBounds().width()/2);
long centerY = hole.getBounds().top + (hole.getBounds().height()/2);
long x = centerX - newCenterX;
long y = centerY - newCenterY;
long aux = (long) ((Math.pow(Math.abs(x),2)) + (Math.pow(Math.abs(y),2)));
long distance = (long) Math.sqrt(aux);
Log.d(LOG_TAG, "[checkDrawContains] x: " + x);
Log.d(LOG_TAG, "[checkDrawContains] y: " + y);
Log.d(LOG_TAG, "[checkDrawContains] distance: " + distance);
Log.d(LOG_TAG, "[checkDrawContains] sum radius: " + (newCenterX + centerX));
long sRads = (newHole.getBounds().width()/2) + (hole.getBounds().width()/2);
if(distance <= sRads ) {
return true;
}
}
return false;
}
/** {#inheritDoc} */
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
generateText(canvas);
for (ShapeDrawable hole : mHoles) {
hole.draw(canvas);
}
invalidate();
}
/** {#inheritDoc} */
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
generateRandomHole();
return true;
}
return super.onTouchEvent(event);
}
}
It should work, but It crashes when the recursion starts to recalculate all the random parameters. Is it a stack overflow? Why is that happening? How it should be solved?
I'm working in a spaceship first person view game. I have a joystick, and when i move the joystick i can move all the objects (asteroids) of the screen simulating that the spaceship is being moved with the joystick.
The game works fine, but now i have a problem. If you are pressing the joystick in the max left position and then you do ACTION_UP and then instantly ACTION_DOWN in the joystick again but in the max right position, the spaceship starts moving to the right at max speed. It is hard to explain it. For example, If you press the joystick in max left position the spaceship is moving -20px per frame to the left and if you press the joystick in the max right position, the spaceship moves to the right +20px per frame.
So, now, if i do a fast max left and max right touch on the joystick, the spaceship does this movement: -20....+20
It is not reallistic movement.
I want to get this movement: -20 -17 -14 -9 -5 0 +5 +9 +14 +17 +20.... I mean a more reallistic spaceship movement. But the problem is that i am not a math or physics expert, and i dont have any idea of how to get that kind of functionality in this joystick... any help will be very grateful.
Here you can find a demo project with the joystick: https://mega.co.nz/#!cp5FhYIT!dM88qx_xQdyhED9fX_4xeJ9ciQYJirUlNzEi-KOzU2k
This is the joystick code, i found it in google and works very well except for the non realistic movement that i described before:
public class Joystick extends View {
public static final int INVALID_POINTER = -1;
private JoystickMovedListener moveListener;
//# of pixels movement required between reporting to the listener
private float moveResolution;
//Max range of movement in user coordinate system
private float movementRange;
//Last touch point in view coordinates
private int pointerId = INVALID_POINTER;
private float touchX;
private float touchY;
private float touchXDelayedMovement;
private float touchYDelayedMovement;
//Handle center in view coordinates
private float handleX;
private float handleY;
//Last reported position in view coordinates (allows different reporting sensitivities)
private float reportX;
private float reportY;
//Center of the view in view coordinates
private int cX;
private int cY;
//Size of the view in view coordinates
private int dimX;
private int dimY;
private int innerPadding;
private int bgRadius;
private int handleRadius;
private int movementRadius;
private int handleInnerBoundaries;
//Cartesian coordinates of last touch point - joystick center is (0,0)
private int cartX;
private int cartY;
//User coordinates of last touch point
private int userX;
private int userY;
//Offset co-ordinates (used when touch events are received from parent's coordinate origin)
private int offsetX;
private int offsetY;
private Paint bgPaint;
private Paint handlePaint;
boolean disabled;
Handler handler;
Handler handlerDelayedMovement;
public Joystick(Context context) {
super(context);
initJoystickView();
}
private void initJoystickView() {
setFocusable(true);
handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
handlePaint.setColor(Color.RED);
handlePaint.setStrokeWidth(1);
handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgPaint.setColor(Color.DKGRAY);
bgPaint.setStrokeWidth(1);
bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
this.moveResolution = 1.0f;
handler = new Handler();
handlerDelayedMovement = new Handler();
}
public void setMovementRange(float movementRange) {
this.movementRange = movementRange;
}
public void setOnJostickMovedListener(JoystickMovedListener listener) {
this.moveListener = listener;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int d = Math.min(getMeasuredWidth(), getMeasuredHeight());
dimX = d;
dimY = d;
cX = d / 2;
cY = d / 2;
bgRadius = dimX/2 - innerPadding;
handleRadius = (int)(d * 0.2);
handleInnerBoundaries = handleRadius;
movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Here we make sure that we have a perfect circle
int measuredWidth = measure(widthMeasureSpec);
int measuredHeight = measure(heightMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
}
private int measure(int measureSpec) {
int result = 0;
// Decode the measurement specifications.
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.UNSPECIFIED) {
result = 200; // Return a default size of 200 if no bounds are specified.
} else {
result = specSize; // As you want to fill the available space always return the full available bounds.
}
return result;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
// Draw the background
canvas.drawCircle(cX, cY, bgRadius, bgPaint);
// Draw the handle
handleX = touchX + cX;
handleY = touchY + cY;
canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
canvas.restore();
}
public void setPointerId(int id) {
this.pointerId = id;
}
public int getPointerId() {
return pointerId;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
if (disabled==true)
break;
return processMoveEvent(ev);
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if ( pointerId != INVALID_POINTER ) {
returnHandleToCenter();
returnHandleToCenterDelayedMovement();
setPointerId(INVALID_POINTER);
}
break;
}
case MotionEvent.ACTION_POINTER_UP: {
if ( pointerId != INVALID_POINTER ) {
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if ( pointerId == this.pointerId ) {
returnHandleToCenter();
returnHandleToCenterDelayedMovement();
setPointerId(INVALID_POINTER);
return true;
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
handlerDelayedMovement.removeCallbacksAndMessages(null);
if ( pointerId == INVALID_POINTER ) {
int x = (int) ev.getX();
if ( x >= offsetX && x < offsetX + dimX ) {
setPointerId(ev.getPointerId(0));
if (disabled==true){
return true;
}
return processMoveEvent(ev);
}
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
if ( pointerId == INVALID_POINTER ) {
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
int x = (int) ev.getX(pointerId);
if ( x >= offsetX && x < offsetX + dimX ) {
setPointerId(pointerId);
return true;
}
}
break;
}
}
return false;
}
private boolean processMoveEvent(MotionEvent ev) {
if ( pointerId != INVALID_POINTER ) {
final int pointerIndex = ev.findPointerIndex(pointerId);
// Translate touch position to center of view
float x = ev.getX(pointerIndex);
touchX = x - cX - offsetX;
float y = ev.getY(pointerIndex);
touchY = y - cY - offsetY;
reportOnMoved();
invalidate();
return true;
}
return false;
}
private void reportOnMoved() {
//constraint circle
float diffX = touchX;
float diffY = touchY;
double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
if ( radial > movementRadius ) {
touchX = (int)((diffX / radial) * movementRadius);
touchY = (int)((diffY / radial) * movementRadius);
}
//We calc user coordinates
//First convert to cartesian coordinates
cartX = (int)(touchX / movementRadius * movementRange);
cartY = (int)(touchY / movementRadius * movementRange);
//Cartesian Coordinates
userX = cartX;
userY = cartY;
if (moveListener != null) {
boolean rx = Math.abs(touchX - reportX) >= moveResolution;
boolean ry = Math.abs(touchY - reportY) >= moveResolution;
if (rx || ry) {
this.reportX = touchX;
this.reportY = touchY;
moveListener.OnMoved(userX, userY);
}
}
}
private void reportOnMovedDelayedMovement() {
//constraint circle
float diffX = touchXDelayedMovement;
float diffY = touchYDelayedMovement;
double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
if ( radial > movementRadius ) {
touchXDelayedMovement = (int)((diffX / radial) * movementRadius);
touchYDelayedMovement = (int)((diffY / radial) * movementRadius);
}
//We calc user coordinates
//First convert to cartesian coordinates
cartX = (int)(touchXDelayedMovement / movementRadius * movementRange);
cartY = (int)(touchYDelayedMovement / movementRadius * movementRange);
//Cartesian Coordinates
userX = cartX;
userY = cartY;
if (moveListener != null) {
boolean rx = Math.abs(touchXDelayedMovement - reportX) >= moveResolution;
boolean ry = Math.abs(touchYDelayedMovement - reportY) >= moveResolution;
if (rx || ry) {
this.reportX = touchXDelayedMovement;
this.reportY = touchYDelayedMovement;
moveListener.OnMoved(userX, userY);
}
}
}
private void returnHandleToCenter() {
final int numberOfFrames = 5;
final double intervalsX = (0 - touchX) / numberOfFrames;
final double intervalsY = (0 - touchY) / numberOfFrames;
handler.removeCallbacksAndMessages(null);
for (int i = 0; i < numberOfFrames; i++) {
final int j = i;
handler.postDelayed(new Runnable() {
#Override
public void run() {
touchX += intervalsX;
touchY += intervalsY;
//reportOnMoved();
invalidate();
if (moveListener != null && j == numberOfFrames - 1) {
moveListener.OnReturnedToCenter();
}
}
}, i * 10);
}
if (moveListener != null) {
moveListener.OnReleased();
}
}
private void returnHandleToCenterDelayedMovement() {
final int numberOfFrames = 25;
touchXDelayedMovement=touchX;
touchYDelayedMovement=touchY;
final double intervalsX = (0 - touchXDelayedMovement) / numberOfFrames;
final double intervalsY = (0 - touchYDelayedMovement) / numberOfFrames;
handlerDelayedMovement.removeCallbacksAndMessages(null);
for (int i = 0; i < numberOfFrames; i++) {
handlerDelayedMovement.postDelayed(new Runnable() {
#Override
public void run() {
touchXDelayedMovement += intervalsX;
touchYDelayedMovement += intervalsY;
reportOnMovedDelayedMovement();
}
}, i * 50);
}
}
public void setInnerPadding(int innerPadding){
this.innerPadding=innerPadding;
}
public void disable(){
disabled=true;
}
public void enable(){
disabled=false;
}
public interface JoystickMovedListener {
public void OnMoved(int pan, int tilt);
public void OnReleased();
public void OnReturnedToCenter();
}
}
You must do this in the class that will use the joystick:
private JoystickMovedListener joystickListener = new JoystickMovedListener() {
#Override
public void OnMoved(int pan, int tilt) {
//here i move the objects in the game
}
}
#Override
public void OnReleased() {}
public void OnReturnedToCenter() {};
};
joystickOnScreen = new Joystick(this);
joystickOnScreen.setMovementRange(screenHeight/50);
joystickOnScreen.setInnerPadding(screenHeight/30);
joystickOnScreen.setOnJostickMovedListener(joystickListener);
RelativeLayout.LayoutParams joystickParams = new RelativeLayout.LayoutParams(sh/3, sh/3);
joystickParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
joystickParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
joystickParams.setMargins(sh/100, 0, 0, sh/100);
joystickOnScreen.setLayoutParams(joystickParams);
joystickOnScreen.setAlpha(0.3f);
I will not implement the changes for you but hopefully this answer can help you towards implementing this on your own.
With your current implementation you are updating the object position (x, y) each frame. To get the more realistic physics that you want, you need to store and update velocity as well (vx, vy).
Add two new variables, vx and vy (with initial values of zero) in the objects that you are currently updating the position for. The joystick should control the change of the velocity instead of the position. Change the code that updates the positions x and y, to update the velocities vx and vy instead. When the joystick is max left, you can for example set vx = vx - 3.
After the velocity is updated, you need to update the position using the velocity variables. For example, set the position x = x + vx. Ideally you want this to happen in a different method that runs even if you don't move the joystick, but to keep it simple you can do this update right after the update of the velocity variables.
With this implementation you will get a more realistic game physics. As a next step you might want to add limits on the velocity to not move too fast. This can be done with an if-statement where you check that the value is not too big before adding more to it, or too smal before subtracting from it. Good luck!