ExoPlayer2 Zoomable View? - android-layout

There are what appear to be several clones of the same answer for the first version of Exoplayer and the original Android media player, but they do not compile on Exoplayer2, which reorganized quite a bit of the internal code.
A reasonably-diligent search has not found anything in the way of a library or example code to do this (e.g. pinch zoom and scroll, etc.) There's plenty of code around to do it for still images (e.g. retrieved through Picasso, etc)
Does anyone have a sample that will build and work with ExoPlayer2?
Thanks in advance!
Update: The problem appears to be that I cannot either subclass or attach a VideoListener to a SimpleExoPlayer instance; attempting to do so leaves you with nothing, as the instance has already attached its own listener which pays exactly zero attention to aspect ratio when a TextureView is involved. This makes the video completely unusable; a listener could correct that quite easily, but there appears to be no way to attach it (the methods to do so are marked deprecated, and if you try to use them anyway you get no video output.)
This code will paint and run the ua.pohohalo.zoomabletextureview (or just a plain TextureView) but I cannot attach a videolistener to it and the default, when it initializes, fits the video to the view size vertically in portrait mode which destroys the aspect ratio. It also has serious glitches if you resize the video below the display window size but I can test for and fix that in polohalo's code. What I've not figured out how to do is to get the original display to honor the original aspect ratio or to attach a VideoListener to set it on init -- it works fine if I use a PlayerView, but that's not able to be extended to support translations. The "VideoListener" prototype in this codeblock should fix the aspect ratio problem -- that's what I've been unable to attach or find a way to set a flag on the original view (which would also do the job) that tells ExoPlayer to honor the original aspect ratio and fit within the screen size.
The call to simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT), which works on a PlayerView, is not valid on a TextureView -- it appears that the mode defaults to RESIZE_MODE_FILL and I cannot find a method to set it to FIT.
package net.cudasystems.android.videotest;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.TextureView;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener;
public class MainActivity extends AppCompatActivity {
private String mURL = "http://point-at-an-mp4-file";
TextureView mPlayerView;
SimpleExoPlayer player = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPlayerView = findViewById(R.id.video_view2);
}
private void initializePlayer() {
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(this, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
VideoListener mVideoListener = new VideoListener() {
#Override
public void onRenderedFirstFrame() {
}
#Override
public void onVideoSizeChanged(int width, int height, int rotation, float pixelWidthHeightRatio ) {
String TAG = "VideoSizeChange";
int viewWidth = mPlayerView.getWidth();
int viewHeight = mPlayerView.getHeight();
double aspectRatio = (double) height / width;
int newWidth, newHeight;
if (viewHeight > (int) (viewWidth * aspectRatio)) {
// limited by narrow width; restrict height
newWidth = viewWidth;
newHeight = (int) (viewWidth * aspectRatio);
} else {
// limited by short height; restrict width
newWidth = (int) (viewHeight / aspectRatio);
newHeight = viewHeight;
}
int xoff = (viewWidth - newWidth) / 2;
int yoff = (viewHeight - newHeight) / 2;
Log.v(TAG, "video=" + width + "x" + height +
" view=" + viewWidth + "x" + viewHeight +
" newView=" + newWidth + "x" + newHeight +
" off=" + xoff + "," + yoff);
Matrix txform = new Matrix();
mPlayerView.getTransform(txform);
txform.setScale((float) newWidth / viewWidth, (float) newHeight / viewHeight);
txform.postTranslate(xoff, yoff);
mPlayerView.setTransform(txform);
}
};
player = ExoPlayerFactory.newSimpleInstance(
renderersFactory,
new DefaultTrackSelector(), new DefaultLoadControl());
player.setVideoTextureView(mPlayerView);
// mPlayerView.setPlayer(player);
player.setPlayWhenReady(true);
Uri uri = Uri.parse(mURL);
MediaSource mediaSource = buildMediaSource(uri);
player.prepare(mediaSource, true, true);
}
private MediaSource buildMediaSource(Uri uri) {
return new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory("exoplayer-codelab")).
createMediaSource(uri);
}
#Override
public void onStart() {
super.onStart();
if (Util.SDK_INT > 23) {
if (player == null) {
initializePlayer();
}
}
}
#Override
public void onResume() {
super.onResume();
if ((Util.SDK_INT <= 23 || player == null)) {
initializePlayer();
}
}
#Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
releasePlayer();
}
}
#Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
releasePlayer();
}
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
}
}
}
and the XML file to go with it.... The zoomable declaration is "on" right now but the code can easily use either the non-zoomable one or the PlayerView (by changing the type and not attaching the texture); that one works perfectly well, including properly handling rotation.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/root"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<TextureView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:id="#+id/video_view3" />
<ua.polohalo.zoomabletextureview.ZoomableTextureView
android:id="#+id/video_view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:maxScale="4"/>
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/video_view"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:use_controller="false"/>
</FrameLayout>
Update: After much head-banging the following code works EXCEPT if you try to use it in a fragment, in which case the TextView extension has problems due to how minimum and maximum scale values get picked up. The obvious "hack" answer is to check for minScale = 0 and force it to 1.0 if you find it un-initialized.
Hope this helps someone else out.
package net.cudasystems.android.videotest;
import android.net.Uri;
import android.os.Bundle;
import android.support.constraint.ConstraintLayout;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.TextureView;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
public class MainActivity extends AppCompatActivity {
private String mURL = "http://set-to-an-mp4-URL"
TextureView mPlayerView;
SimpleExoPlayer player = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPlayerView = findViewById(R.id.video_view);
}
private void initializePlayer() {
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(this, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
player = ExoPlayerFactory.newSimpleInstance(
renderersFactory,
new DefaultTrackSelector(), new DefaultLoadControl());
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// Make sure the initial aspect ratio is 16:9 (otherwise a TextureView init's to the LARGER of
// the two dimensions of the video irrespective of the orientation setting and screws the aspect ratio!)
int width = metrics.widthPixels;
int newHeight = (width * 9) / 16;
mPlayerView.setLayoutParams(new ConstraintLayout.LayoutParams(width, newHeight));
mPlayerView.invalidate();
player.setVideoTextureView(mPlayerView);
player.setPlayWhenReady(true);
Uri uri = Uri.parse(mURL);
MediaSource mediaSource = buildMediaSource(uri);
player.prepare(mediaSource, true, true);
}
private MediaSource buildMediaSource(Uri uri) {
return new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory("exoplayer-codelab")).
createMediaSource(uri);
}
#Override
public void onStart() {
super.onStart();
if (Util.SDK_INT > 23) {
if (player == null) {
initializePlayer();
}
}
}
#Override
public void onResume() {
super.onResume();
if ((Util.SDK_INT <= 23 || player == null)) {
initializePlayer();
}
}
#Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
releasePlayer();
}
}
#Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
releasePlayer();
}
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
}
}
}
And the working XML to go with it:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/root"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:keepScreenOn="true">
<ua.polohalo.zoomabletextureview.ZoomableTextureView
android:id="#+id/video_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:maxScale="4"
app:minScale="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Does This work?"
android:textColor="#android:color/holo_red_dark"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>

Related

OnScrollChangeListener for ScrollView

I would like to make a function to intercept a certain scroll at the top of the view.
to do this I'm trying to use OnScrollChangeListener.
My view contains a ScrollView
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/scrollViewClientPhysique"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/fond"
tools:context=".client.FicheClient">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">**strong text**
and I initialize addOnScrollChangedListener in a function I call inside onCreateView
fun initializeInfiniteScroll(){
val scrollView = myView.findViewById<View>(R.id.scrollViewClientPhysique) as ScrollView
scrollView.viewTreeObserver.addOnScrollChangedListener {
if (scrollView != null) {
val view = scrollView.getChildAt(scrollView.childCount - 1)
val diff =
view.bottom + scrollView.paddingBottom - (scrollView.height + scrollView.scrollY)
if (diff == 0) {
// do stuff
}
}
}
}
but when when I scroll the view I don't enter addOnScrollChangedListener to intercept how many dp the scroll is.
what am I doing wrong?
Please update your ScrollChangedListener as mentioned below.
public class MainActivity extends AppCompatActivity implements View.OnTouchListener,
ViewTreeObserver.OnScrollChangedListener {
ScrollView scrollView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
scrollView = findViewById(R.id.scrollView);
scrollView.setOnTouchListener(this);
scrollView.getViewTreeObserver().addOnScrollChangedListener(this);
}
public void onScrollChanged(){
View view = scrollView.getChildAt(scrollView.getChildCount() - 1);
int topDetector = scrollView.getScrollY();
int bottomDetector = view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY());
//TODO: Just added for testing/understanding. Please add/replace your own logic..
if(bottomDetector == 0 ){
Toast.makeText(this,"Scroll View bottom reached",Toast.LENGTH_SHORT).show();
}
if(topDetector <= 0){
Toast.makeText(this,"Scroll View top reached",Toast.LENGTH_SHORT).show();
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
}

How to implement CountDownTimer in Recyclerview?

Hi I'm working on a small application which sets a countdown. Now I want this countdown to be seen in front of a background inside a recyclerview. So in the end a user will have set multiple countdown timers and these will be displayed with backgrounds inside a recyclerview.
What I have made right now is far from perfect, but it's in the right direction of what I want it to be. The only problem I have right now is, because I'm working with a recyclerview, the view (including the text with the countdown) will be recycled. So if I scroll down, the countdown will be reset. I think that's the problem I'm having but I have no clue how to solve it. This is the most important code:
The activity the recyclerview is in:
package anotherChallenge.example.criminalactivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.DatePicker;
import com.example.criminalactivity.R;
import java.text.DateFormat;
import java.util.Calendar;
public class Overview extends AppCompatActivity {
private int[] allImages = {
R.drawable.festive1_pictures,
R.drawable.festive2_pictures,
R.drawable.festive3_pictures,
R.drawable.festive4_pictures,
R.drawable.festive5_pictures,
R.drawable.ferrari_materialistic_pictures,
R.drawable.house_materialistic_pictures,
R.drawable.boat_materialistic_pictures,
R.drawable.rolex_materialistic_pictures,
R.drawable.private_jet_materialistic_pictures,
R.drawable.holiday1_pictures,
R.drawable.holiday2_pictures,
R.drawable.holiday3_pictures,
R.drawable.holiday4_pictures,
R.drawable.holiday5_pictures,
R.drawable.meet1_pictures,
R.drawable.meet2_pictures,
R.drawable.meet3_pictures,
R.drawable.meet4_pictures,
R.drawable.meet5_pictures,
R.drawable.other1_pictures,
R.drawable.other2_pictures,
R.drawable.other3_pictures,
R.drawable.other4_pictures,
R.drawable.other5_pictures,
};
private RecyclerView recyclerView2;
private DatePicker datePicker;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_overview);
// textClock.setFormat12Hour(null);
// textClock.setFormat24Hour("EEE/MMM d/yyyy hh:mm:ss");
recyclerView2 = findViewById(R.id.recyclerview2);
datePicker = findViewById(R.id.datepicker);
Calendar calendar = Calendar.getInstance();
String currentDate = DateFormat.getDateInstance(DateFormat.FULL).format(calendar.getTime());
System.out.println(currentDate);
// String filledInDate = datePicker.getDayOfMonth() + "/" + datePicker.getMonth() + "/"+
// datePicker.getYear();
adapterForOverview adapter = new adapterForOverview(this, allImages);
recyclerView2.setAdapter(adapter);
recyclerView2.setLayoutManager(new LinearLayoutManager(this));
}
}
Adapter for the recyclerview:
package anotherChallenge.example.criminalactivity;
import android.content.Context;
import android.os.CountDownTimer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.example.criminalactivity.R;
public class adapterForOverview extends RecyclerView.Adapter<adapterForOverview.MyViewHolder> {
AddItem addItem;
int[] images;
Context context;
CustomAdapterCardView customAdapterCardView;
public adapterForOverview(Context context, int[] images) {
this.context=context;
this.images=images;
addItem = new AddItem();
// customAdapterCardView = new CustomAdapterCardView(context,
customAdapterCardView.getArrayList());
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
// holder.cardView.setCardBackgroundColor(Color.parseColor("#696969"));
new CountDownTimer(30000,1000) {
#Override
public void onTick(long millisUntilFinished) {
holder.myTextview.setText("The title will be here \n" + millisUntilFinished/1000);
}
#Override
public void onFinish() {
holder.myTextview.setText("Achieved!");
}
}.start();
holder.myImage.setImageResource(images[position]);
// holder.myImage.setImageDrawable(addItem.getDrawable());
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.itemsinoverview,parent,false);
return new MyViewHolder(view);
}
#Override
public int getItemCount() {
return images.length;
}
public class MyViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
ImageView myImage;
TextView myTextview;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
cardView = itemView.findViewById(R.id.cardview2);
myImage = itemView.findViewById(R.id.imageView);
myTextview = itemView.findViewById(R.id.textviewOfCountdown);
}
}
}
XML Code of the item inside the recyclerview:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<androidx.cardview.widget.CardView
android:id="#+id/cardview2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="4dp"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintlayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="#+id/textviewOfCountdown"
android:layout_width="match_parent"
android:layout_height="100dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center"
android:textColor="#ffffff"
android:textSize="25sp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Explanation:
As you know android's recyclerView recycles its views and reuses its viewHolders by calling onBind each time to update the item's data. I noticed that in onBindViewHolder
you create a CountDownTimer every time any data is bound, so you end up with multiple timers updating the same ViewHolder.
Bad solution:
One solution would be to make the adapter's items non-recyclable, but that wouldn't be optimal and negates the recycling ability of the recyclerview.
Good Solution:
The solution is to keep a reference to your timer inside your viewHolder called MyViewHolder. Then in onBindViewHolder check if the timer has already been instantiated. If yes, then cancel the previous timer and afterwards create again a new timer. If not, then there's no need to cancel anything you just proceed to the creation of a new timer object for the first time.
public void onBindViewHolder(#NonNull MyViewHolder holder, final int position) {
if (holder.timer != null) {
holder.timer.cancel();
}
holder.timer = new CountDownTimer(30000, 1000) {
...
}.start();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
...
CountDownTimer timer;
...
}
The problem is that you're creating a new timer every time a new view is bound. You want to keep the data the adapterr is going to display separate from the view that will display it. In this case, you could manage the countdown timer in an object that represents each item in the list. For example (pseudocode):
class MyItem {
val imageResource: Int
val timer: CountDownTimer
}
In the adapter, you hold a list of MyItem and instead of creating a brand new timer, you just set the current value of the existing timer (pseudocode):
public class adapterForOverview extends RecyclerView.Adapter<adapterForOverview.MyViewHolder> {
MyItem[] items;
Context context;
public adapterForOverview(Context context, MyItem[] items) {
this.context=context;
this.items=items;
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
timer = items[position].timer
if (timer.isFinished()) {
holder.myTextview.setText("The title will be here \n" + millisUntilFinished/1000);
} else {
holder.myTextview.setText("Achieved!");
}
}
#Override
public int getItemCount() {
return items.length;
}
Finally you would create the timer wherever you're currently initializing the images now. Instead of updating any views, the on tick and finished method just tell the adapter to refresh that specific item in the list, which will then rebind the current timer values (pseudocode):
for (int i = 0; i < allImages.length; i++) {
items[i] = new MyItem()
items[i].image = allImages[i]
items[i].timer = new CountDownTimer(30000,1000) {
#Override
public void onTick(long millisUntilFinished) {
adapter.notifyItemChanged(i)
}
#Override
public void onFinish() {
adapter.notifyItemChanged(i)
}
}.start();
}
Again, this is all pseudo code and won't compiler, but should give you an idea of how to structure your code to solve your problem.

How do I Remove Cell Padding in a Gridview

I'm hoping someone can help me. I have created a Sudoku app. I have a grid view that shows string text from a list view. I have a problem with my layout of the Grid View on my phone and tablet; , see 1st and 2nd screens below .
What I'm trying to do is set the screen, so the grid display neatly just above the numbers 1 to 5. Ideally I'd like it to render appropriately on tablet and phone.
On a Phone the last row of the sudoku grid showing underneath the first set of button button See firstImage. Whereas on a Tablet there is a huge gap between the gridvview and the Buttons
What I have I tried so far:
I tried android:layout_above btn1. But that chopped off the last row of the sudoku grid and I had to drag the grid up and down to see the last row on a phone. It also caused the app to crash of a phone. Not good.
I tried putting a frame layout inside the relative layout and putting the gridview inside of that. But that had the also chopped off the last row of the sudoku grid as per layout_above.
Really what I'd like to do is, on a phone I would like to remove or reduce the padding above and below each number in each cell in the grid view. As the cell padding makes each cell in the grid 2 - 3 times bigger than it needs to be. This is a problem on a 5" mobile phone screen. I have tried the following and none of them worked.
How do I get rid of unwanted padding in a Listview row
android:gravity - Made no Difference
How to avoid space between imageview and gridview in linearlayout
listSelector=”#null” - Made no difference
Removing the extra padding in a GridView in android
stretchMode(0); - Everything disappeared.
How do I get rid of unwanted padding in a Listview row
view.setMinimumHeight(0); - Made no difference.
I also tried view.setPadding(); it was useful in that the padding on the left hand side was removed, but it didn't remove the papdding at the top or bottom.
view.setPadding(dpToPx(0, parent), 0, 0, 0);
I am at a loss at this stage about how to move this forward and am worried that trying all these different things is making me more confused. If someone could point me in the correct direction I'd be very grateful. Code is shown below.
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.example.admin1.sudoku.MainActivity"
android:background="#663399"
tools:showIn="#layout/activity_main">
<Button
android:id="#+id/btn1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn1Label"
android:layout_above="#+id/btn6"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignBottom="#+id/btn2"
/>
<Button
android:id="#+id/btn2"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn2Label"
android:layout_above="#+id/btn7"
android:layout_toLeftOf="#+id/btn8"
android:layout_toStartOf="#+id/btn8" />
<Button
android:id="#+id/btn3"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn3Label"
android:layout_alignTop="#+id/btn2"
android:layout_toRightOf="#+id/btn2"
android:layout_toEndOf="#+id/btn2" />
<Button
android:id="#+id/btn4"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn4Label"
android:layout_alignTop="#+id/btn3"
android:layout_toRightOf="#+id/btn3"
android:layout_toEndOf="#+id/btn3" />
<Button
android:id="#+id/btn5"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn5Label"
android:layout_alignTop="#+id/btn4"
android:layout_toRightOf="#+id/btn4"
android:layout_toEndOf="#+id/btn4" />
<Button
android:id="#+id/btn6"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn6Label"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<Button
android:id="#+id/btn7"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn7Label"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/btn6"
android:layout_toEndOf="#+id/btn6" />
<Button
android:id="#+id/btn8"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn8Label"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/btn7"
android:layout_toEndOf="#+id/btn7" />
<Button
android:id="#+id/btn9"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn9Label"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/btn3"
android:layout_toEndOf="#+id/btn3" />
<Button
android:id="#+id/btn0"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btn0Label"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/btn9"
android:layout_toEndOf="#+id/btn9" />
<Button
android:id="#+id/btnCheck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btnCheckLabel"
android:layout_below="#+id/gridView1"
android:layout_alignTop="#+id/btn5"
android:layout_toRightOf="#+id/btn5"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
<Button
android:id="#+id/btnHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/btnHintLabel"
android:layout_alignTop="#+id/btn0"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignLeft="#+id/btnCheck"
android:layout_alignStart="#+id/btnCheck" />
<GridView
android:id="#+id/gridView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numColumns="9"
android:columnWidth="0dp"
android:horizontalSpacing="5dp"
android:verticalSpacing="5dp"
android:clipChildren="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:background="#C0C0C0"
android:padding="5dp"
android:textSize="12sp"
android:stretchMode="columnWidth"
android:gravity="clip_vertical"
/>
</RelativeLayout>
MainActivity.java
package com.example.admin1.sudoku;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.DisplayMetrics;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.GridView;
import android.widget.ArrayAdapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.graphics.Color;
import android.widget.Toast;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import android.content.res.Resources;
import android.media.MediaPlayer;
public class MainActivity extends AppCompatActivity {
// Class that holds the creation of a Sudoku Puzzle
private SudokuPuzzle clsSudoku;
//Variables used throughout the program.
private final int iMaxCells = 81;
private String[] stResult; // Holds the solution to the current puzzle
private int[] iExcluded; // Holds all the blue cells (clues given to the user at the start)
// Placed into List for fast searching
private int iElement;
private boolean blShowResult; // Indicates if the user has clicked Show Result
private boolean blGiveHint; // Indicates if the user has clicked Give Hint
private boolean blSoundOn;
// UI Elements
private View tvCell;
private GridView gridView;
private Menu menu;
private MediaPlayer mp = null;
/* Lists
lstitems holds all the items in the current board including user entries
lstExclude holds all the blue cells (clues given to the user at the start)
adapter
*/
private List<String> lstItems;
private ArrayAdapter<String> adapter ;
private List<Integer> lstExcluded;
public MainActivity() {
stResult = new String[iMaxCells];
blGiveHint = false;
blShowResult = false;
iElement = 0;
blSoundOn = false;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Declare used buttons
Button btn1 = (Button) findViewById(R.id.btn1);
Button btn2 = (Button) findViewById(R.id.btn2);
Button btn3 = (Button) findViewById(R.id.btn3);
Button btn4 = (Button) findViewById(R.id.btn4);
Button btn5 = (Button) findViewById(R.id.btn5);
Button btn6 = (Button) findViewById(R.id.btn6);
Button btn7 = (Button) findViewById(R.id.btn7);
Button btn8 = (Button) findViewById(R.id.btn8);
Button btn9 = (Button) findViewById(R.id.btn9);
Button btn0 = (Button) findViewById(R.id.btn0);
Button btnHint = (Button) findViewById(R.id.btnHint);
Button btnCheck = (Button) findViewById(R.id.btnCheck);
//Creates a new Game
clsSudoku = new SudokuPuzzle();
newGame();
gridView = (GridView) findViewById(R.id.gridView1);
gridView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
if (blGiveHint == true){
blGiveHint = false;
//Initialise any hinted cells back to white
clearCells();
}
if (!blShowResult){
tvCell = gridView.getChildAt(iElement);
try{
tvCell.setBackgroundColor(Color.WHITE);
iElement = position;
tvCell = gridView.getChildAt(iElement);
tvCell.setBackgroundColor(Color.RED);
}
catch(Exception e){
}
}
}
});
gridView.setAdapter(adapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, lstItems) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
view.setPadding(dpToPx(0, parent), 0, 0, 0);
view.setMinimumHeight(0);
if (position == iElement && !blShowResult){
view.setBackgroundColor(Color.RED);
}
else if (lstExcluded.contains(position)) {
view.setBackgroundColor(Color.WHITE);
}
else{
view.setBackgroundColor(Color.CYAN);
}
return view;
}
#Override
public boolean isEnabled(int position) {
// Item position which you want to disable.
if (!lstExcluded.contains(position) ) {
return false;
}
else {
return true;
}
}
});
btn1.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "1");
adapter.notifyDataSetChanged();
}
}
});
btn2.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "2");
adapter.notifyDataSetChanged();
}
}
});
btn3.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "3");
adapter.notifyDataSetChanged();
}
}
});
btn4.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "4");
adapter.notifyDataSetChanged();
}
}
});
btn5.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "5");
adapter.notifyDataSetChanged();
}
}
});
btn6.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "6");
adapter.notifyDataSetChanged();
}
}
});
btn7.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "7");
adapter.notifyDataSetChanged();
}
}
});
btn8.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "8");
adapter.notifyDataSetChanged();
}
}
});
btn9.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "9");
adapter.notifyDataSetChanged();
}
}
});
btn0.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if (adapter.isEnabled(iElement) && !blShowResult){
lstItems.set(iElement, "");
adapter.notifyDataSetChanged();
}
}
});
btnHint.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
blGiveHint = true;
giveHint();
}
});
btnCheck.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
checkCorrect();
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
this.menu = menu;
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
switch(id){
case R.id.new_game:
createNewGame();
return true;
case R.id.show_result:
getResult();
return true;
case R.id.sound_toggle:
setSoundSettings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// Populate a list for fast searching later
private void populateExcludeList(int[] ipiArray){
lstExcluded.clear();
for (int iCnt = 0; iCnt < ipiArray.length; iCnt++){
lstExcluded.add(ipiArray[iCnt] - 1);
}
Collections.sort(lstExcluded);
}
// Populate a list for fast searching later
private void setBoard(String[] ipstArray){
lstItems.clear();
for (int iCnt = 0; iCnt < ipstArray.length; iCnt++){
lstItems.add(ipstArray[iCnt]);
}
}
// Create a new puzzle
private void newGame(){
String[] stNumbers = new String[iMaxCells];
blShowResult = false;
clsSudoku.swapTwoNumbers();
clsSudoku.swapTwoColumns();
clsSudoku.swapTwoBlocks();
clsSudoku.rotateNintyDegrees();
clsSudoku.clearNumbers();
stNumbers = clsSudoku.getResult("User");
stResult = clsSudoku.getResult("Result");
iExcluded = clsSudoku.getRemoved();
if (lstItems == null) {
lstItems = new ArrayList<String>(Arrays.asList(stNumbers));
}
else {
setBoard(stNumbers);
}
lstExcluded = new ArrayList<Integer>();
populateExcludeList(iExcluded);
iElement = lstExcluded.get(0);
}
private void createNewGame(){
if ( !lstItems.isEmpty() ){
lstItems.clear();
}
newGame();
adapter.notifyDataSetChanged();
}
private void getResult(){
blShowResult = true;
setBoard(stResult);
lstExcluded = new ArrayList<Integer>();
populateExcludeList(iExcluded);
adapter.notifyDataSetChanged();
}
public void giveHint(){
for (int iCnt = 0; iCnt < iMaxCells; iCnt++){
tvCell = gridView.getChildAt(iCnt);
try{
if (lstItems.get(iCnt).equalsIgnoreCase(stResult[iCnt]) == false && blGiveHint == true){
tvCell.setBackgroundColor(Color.RED);
}
else if(lstItems.get(iCnt).equalsIgnoreCase(stResult[iCnt]) && lstExcluded.contains(iCnt) ){
tvCell.setBackgroundColor(Color.WHITE);
}
}
catch(Exception e){
Toast.makeText(this, "Error: " + e.getMessage(),Toast.LENGTH_LONG).show();
}
}
}
public void checkCorrect(){
boolean blErrorFound = false;
int iCntErrors = 0;
int iCntBlanks = 0;
String stMessage = "";
for (int iCnt = 0; iCnt < iMaxCells; iCnt++){
if ( (lstItems.get(iCnt).equalsIgnoreCase("") == true)
){
iCntBlanks = iCntBlanks + 1;
blErrorFound = true;
}
else if((lstItems.get(iCnt).equalsIgnoreCase(stResult[iCnt]) == false) &&
(lstItems.get(iCnt).equalsIgnoreCase("") == false)
){
iCntErrors = iCntErrors + 1;
blErrorFound = true;
}
}
if (!blErrorFound){
stMessage = "Congratulations !!! Your solution is correct";
if (blSoundOn == true){
playSound("congratulations");
}
}
else{
stMessage = "You have " + iCntErrors + " errors and " + iCntBlanks + " squares left to do";
if (blSoundOn == true){
playSound("wrong");
}
}
Toast.makeText(getApplicationContext(), stMessage, Toast.LENGTH_LONG).show();
}
private void clearCells(){
for (int iCnt = 0; iCnt < iMaxCells; iCnt++){
tvCell = gridView.getChildAt(iCnt);
if(lstExcluded.contains(iCnt) ){
tvCell.setBackgroundColor(Color.WHITE);
}
}
}
private void setSoundSettings() {
MenuItem miMenu = menu.findItem(R.id.sound_toggle);
if (blSoundOn) {
blSoundOn = false;
miMenu.setTitle("Turn Sound On");
} else {
blSoundOn = true ;
miMenu.setTitle("Turn Sound Off");
}
}
public void playSound(String stFileName){
int iSoundId;
if (mp != null){
mp.reset();
mp.release();
}
Resources res = getApplicationContext().getResources();
iSoundId = res.getIdentifier(stFileName, "raw", getApplicationContext().getPackageName());
mp = MediaPlayer.create(getApplicationContext(), iSoundId);
mp.start();
}
public int dpToPx(int dp, View v) {
DisplayMetrics displayMetrics = v.getContext().getResources().getDisplayMetrics();
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
}

Moving ball with accelerometer inside a view on camera preview in android

I have been searching and coding for 3 days now on this problem, no result :(
I made a camera overlay which the code is here + the Accelerometer
public class CameraActivity extends Activity implements SurfaceHolder.Callback, SensorEventListener {
Camera camera;
SurfaceView surfaceView;
SurfaceHolder surfaceHolder;
boolean previewing = false;
LayoutInflater controlInflater = null;
static String TAG = CameraActivity.class.getSimpleName();
// Accelerometer
private SensorManager mSensorManager;
private Sensor mAccelerometer;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.surface_view);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
surfaceView = (SurfaceView) findViewById(R.id.camerapreview);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
controlInflater = LayoutInflater.from(getBaseContext());
View viewControl = controlInflater.inflate(R.layout.camera_control,
null);
LayoutParams layoutParamsControl = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
this.addContentView(viewControl, layoutParamsControl);
// Accelerometer
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent event) {
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
if (previewing) {
camera.stopPreview();
previewing = false;
}
if (camera != null) {
try {
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
previewing = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
camera.stopPreview();
camera.release();
camera = null;
previewing = false;
}
}
Then in the camera_control.xml I have this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout_holder"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/transparent"
android:gravity="bottom" >
<ImageView
android:id="#+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/camera_red_button"
android:layout_alignLeft="#+id/camera_back_button"
android:layout_marginBottom="27dp"
android:src="#drawable/camera_accelerometer_red" />
</RelativeLayout>
and in the surface_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/camerapreview_holder"
>
<SurfaceView
android:id="#+id/camerapreview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
The Image View is a image for spirit level.
The idea is user needs to level the camera before she takes a photo. I have cleared all the un-nessessery code. The above code are working code.
Question:
I need to make a ball for the spirit level. it doesnt need to be the smoothest spirit level. If I can find the way to confine the ball within the spirit level and move it based on the Accelerometer results I will be as happy as Larry :)/
If someone please put me in the right direction regarding these 3 things:
confining the animation within an area
confinement area to be Circle, how to make it
Thanks in advance.
H.
Ok I have got the answer. Hope it can help someone. You need to use calculate the distance from the middle to the corner (radius) the offset the corners.
Also I have change the Accelerometor to Orientation Sensor which then I can get the 3d rotation of the phone in the X and Y Axis.
The following code is working code and I tested them. I am using the Android 2.3.3+
The ball movement is not very smooth as this is not the purpose of the application.
I think in order to smooth the movement you might need to add the timer and collision detection to it too. Please check the android sample.
I haven't refactor the code too yet. So this is not a production level code :)
Codes:
public class CameraActivity extends Activity implements SurfaceHolder.Callback,
SensorEventListener {
// Accelerometer
private SensorManager mSensorManager;
private Sensor mAccelerometer;
/** Called when the activity is first created. */
public static float x;
public static float y;
FrameLayout layout_holder;
FrameLayout ball_holder;
// private float hOriginSize;
float halfOfWidth;
int centerYOnImage;
private Sensor mOrientation;
// float viewInset = 14.0f; // I remove this simply to make the code cleaner. I used this to calculate the radius and the offset later on
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.surface_view);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
surfaceView = (SurfaceView) findViewById(R.id.camerapreview);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
controlInflater = LayoutInflater.from(getBaseContext());
View viewControl = controlInflater.inflate(R.layout.camera_control,
null);
LayoutParams layoutParamsControl = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
this.addContentView(viewControl, layoutParamsControl);
// Accelerometer
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.ball);
CustomDrawableView mCustomDrawableView = new CustomDrawableView(this,
bitmap);
ball_holder = (FrameLayout) findViewById(R.id.ball_holder);
ball_holder.addView(mCustomDrawableView);
halfOfWidth = 40; // You can calculate this, I just put this so I can test it. This is the half of the width of target image - attached in the question
centerYOnImage = 40; // Not important :)
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
// This method will update the UI on new sensor events
public void onSensorChanged(SensorEvent event) {
// float azimuth_angle = event.values[0];
x = event.values[1];
y = event.values[2];
// FIXME: Fine tune this for the image taking part
float ratio = 70.0f / 25.0f;
x = x * ratio;
y = y * ratio;
Log.d(TAG, "x and y: " + x + " " + y);
float maxDistance = 35; // To calculate this halfOfWidth - viewInset;
// to calculate between 2 distances
float distance = (float) Math.sqrt(((x) * (x)) + ((y) * (y)));
if (distance > maxDistance) {
float angle = (float) Math.atan2(x, y);
/ Get new point on the edge of the circle
y = (float) (Math.cos(angle) * maxDistance);
x = (float) (Math.sin(angle) * maxDistance);
}
x = x + 40; // 40 is the half od the distance of the full width
y = (y * -1.0f) + 40; // -1.0f is so orientation works like the actual spirit level
canUserTakePhoto(distance);
}
// Change the background
public void canUserTakePhoto(float treshold) {
if (treshold > 10) {
// Not Yet
} else {
// take it
}
}
#Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(this, mOrientation,
SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
public class CustomDrawableView extends ImageView {
Bitmap b;
public CustomDrawableView(Context context, Bitmap bitmap) {
super(context);
this.b = bitmap;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(this.b, x, y, null);
invalidate();
}
}
#Override
public void onDestroy() // main thread stopped
{
super.onDestroy();
// wait for threads to exit before clearing app
System.runFinalizersOnExit(true);
// remove app from memory
android.os.Process.killProcess(android.os.Process.myPid());
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
if (previewing) {
camera.stopPreview();
previewing = false;
}
if (camera != null) {
try {
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
previewing = true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
camera = Camera.open();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
camera.stopPreview();
camera.release();
camera = null;
previewing = false;
}
}
Cheers.

Android LayoutInflater fails quietly for LinearLayout

I'm using a LayoutInflater to retrieve a view from the resources, then insert it into a programmaticly constructed custom ViewGroup. It works fine when the resource contains only a TextView. But when the resource contains a LinearLayout it fails quietly - no exception is thrown, the custom ViewGroup components appear but the View from the resource does not appear.
The custom ViewGroup puts coloured borders around an inner View. Here is the class:
package ask.question;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class BorderViewGroup extends ViewGroup {
private View leftBorder ;
private View topBorder ;
private View rightBorder ;
private View bottomBorder ;
private View innerView = null ;
public BorderViewGroup(Context context) {
super(context);
initBorderViewGroup(context);
}
public BorderViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initBorderViewGroup(context);
}
public BorderViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initBorderViewGroup(context);
}
private void initBorderViewGroup(Context context) {
leftBorder = new View(context);
topBorder = new View(context);
rightBorder = new View(context);
bottomBorder = new View(context);
addView(leftBorder);
addView(topBorder);
addView(rightBorder);
addView(bottomBorder);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Should probably use argument 'changed'.
int width = r - l;
int height = this.getHeight();
leftBorder.layout(0, 0, 2, height);
topBorder.layout(0, 0, width, 2);
rightBorder.layout(width-2, 0, width, height);
bottomBorder.layout(0, height-2, width, height);
if (innerView != null) {
innerView.layout(2, 2, width-2, height-2);
}
}
/*
* Sets the view to be displayed within the borders.
* Subsequent calls will replace the view.
* #param view View to be displayed. May be null.
*/
public void setInnerView(View view) {
if (innerView!=null) {
this.removeView(innerView);
}
this.innerView = view ;
if (innerView!=null) {
addView(innerView);
}
}
/*
* Sets the colors for each of the four borders.
* A color value of zero will be ignored, leaving that border unchanged.
* Note: colors can be specified in hexadecimal. Red is 0xFFFF0000. In general, the first two hex digits control transparency, the next red, green and blue.
*/
public void setBorderColors(int leftColor, int topColor, int rightColor, int bottomColor) {
if (leftColor!=0) leftBorder.setBackgroundColor(leftColor);
if (topColor!=0) topBorder.setBackgroundColor(topColor);
if (rightColor!=0) rightBorder.setBackgroundColor(rightColor);
if (bottomColor!=0) bottomBorder.setBackgroundColor(bottomColor);
}
/*
* Sets the color of the border around the innerView.
* A color value of zero will be ignored, leaving the border unchanged.
* Note: colors can be specified in hexadecimal. Red is 0xFFFF0000. In general, the first two hex digits control transparency, the next red, green and blue.
*/
public void setBorderColor(int color) {
setBorderColors(color, color, color, color);
}
}
It works fine when the resource is a TextView. Here is the Activity's onCreate() method, and the resource file flat.xml.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BorderViewGroup bvg = new BorderViewGroup(this);
LayoutInflater inflater = LayoutInflater.from(this);
//View mainView = inflater.inflate(R.layout.flat, null);
View mainView = inflater.inflate(R.layout.nested, null);
View objectView = mainView.findViewById(R.id.object_view);
if (mainView==null) throw new RuntimeException("mainView is null");
if (objectView==null) throw new RuntimeException("objectView is null");
bvg.setBorderColors(0xFFFF0000, 0xFF00F0F0, 0xFF00FF00, 0xFF0000FF);
setContentView(bvg);
bvg.setInnerView(objectView);
}
and
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/object_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="View from resource xml." />
The above works fine. It displays coloured borders, and the text "View from resource xml.".
But it fails quietly if the resource file contains a LinearLayout. Here is the Activity's onCreate() method, and the resource file nested.xml.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BorderViewGroup bvg = new BorderViewGroup(this);
LayoutInflater inflater = LayoutInflater.from(this);
View mainView = inflater.inflate(R.layout.flat, null);
//View mainView = inflater.inflate(R.layout.nested, null);
View objectView = mainView.findViewById(R.id.object_view);
if (mainView==null) throw new RuntimeException("mainView is null");
if (objectView==null) throw new RuntimeException("objectView is null");
bvg.setBorderColors(0xFFFF0000, 0xFF00F0F0, 0xFF00FF00, 0xFF0000FF);
setContentView(bvg);
bvg.setInnerView(objectView);
}
and
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/object_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/tv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="A TextView from resource XML." />
<TextView
android:id="#+id/here"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="A second TextView." />
</LinearLayout>
This does not work. It displays the coloured borders, but not the text.
Why does one work, and not the other? Even better, how can the LinearLayout be made to work?
I've followed the inflater code from XML Inflater not seeing any of the views?. I've removed some arguments from the inflation because they cause already-have-parent errors. The TextView works without them.

Resources