How to dynamically reference an XML id to play a sound - java

I'm trying to make a soundboard, such that when you press a button with a particular android:id it will play an .ogg file of the same name. I have the layout setup, just need assistance with referencing the XML id.
For instance, if I have this particular button setup
<Button
android:id="#+id/file022"
android:layout_weight="1"
android:layout_margin="1dp"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:text="Sample Text"
android:textColor="#000000"
android:textStyle="normal"
android:onClick="playSound"
android:background="#B3EDEDED"/>
I could use the following code to play back the corresponding sound to play file022.ogg
(v.getId()==R.id.button14){
MediaPlayer mpSound = MediaPlayer.create(this, R.raw.file022);
mpSound.start();
What do I need to change such that I can avoid hardcoding case statements for 'R.raw.file001', 'R.raw.file002', and instead dynamically reference the id as set in the XML?

You should do this using instead the tag attribute, as retrieving the field name for the ID would require reflection.
<Button
...
android:tag="file022"
... />
Then, in your code, you just retrieve the tag, and get the raw resource identifier for that name.
String tag = (String) v.getTag();
int resId = getResources().getIdentifier(tag, "raw", getPackageName());
MediaPlayer mpSound = MediaPlayer.create(this, resId);
mpSound.start();

For your button, you can give it a tag to identify the sound resource to play
<Button
android:id="#+id/file022"
... contents skipped for brevity
android:tag="file022" <--- add a tag which maps to your sound resource name
/>
Then assign a View.OnClickListener to the button in your Activity (or Fragment).
For the implementation of onClick(View), you can do something similar to this:
public void onClick(View v){
int soundResourceId = [yourContextObject].getResources().getIdentifier(v.getTag(), "raw", this.getPackageName());
// play the sound
MediaPlayer mpSound = MediaPlayer.create(this, soundResourceId );
// etc...
}
Note: this assumes that the tag is set and the OnClickListener is only assigned to buttons that play the sound. Also, i used a placeholder [yourContextObject], so you can use YourActivity.this or if its in a Fragment getContext() so you can access the app resources
This might be a little tedious to maintain if you change your resource names though since you would have to change the value of the android:tag attribute in your view.
Credit to this StackOverflow thread for the string to resource conversion: Android: Howto get a resource id of an android resource
Note: Resources.getResource(...) might be a bit slow, instead you can store the R file resource references in a static final array in your class and create a mapping between the array and your buttons by storing an index instead of the file name in the android:tag, and just use: int soundResourceId = yourResourceArray[Integer.parseInt(v.getTag())] to get the index for the array
EDIT: another alternative if you wanna stick with the tag == file name is to use something like this:
Android, getting resource ID from string?

Related

App crashes on using findViewWithTag and getTag

I am a novice android developer.
I have declared a 3x3 gridLayout in my activity layout, in which I have put 9 imageViews with tags from 0 to 8.
A snippet of the layout code is posted below:
<GridLayout
android:id="#+id/gridLayout"
android:background="#drawable/board"
android:columnCount="3"
android:rowCount="3">
<ImageView
android:id="#+id/counter_topLeft"
android:onClick="drop_in"
android:tag="0"
android:layout_column="0"
android:layout_row="0" />
In my main method, in the method drop_in(), I'm trying to check if user has called it or not, and if the system has called it, I'm trying to retrieve the ImageView by tags, by using a random number as tag.
Here is the drop_in() method:
public void drop_in(View view) // method invoked on tapping any grid cell
{
int i;
ImageView counter = (ImageView) view;
i = Integer.parseInt(counter.getTag().toString()); // getting the associated tags or basically cell number
if(isHuman)
{ // do some stuff and then
isHuman=false;
}
// now app will perform actions and wait for user input to do stuff again
if(!isHuman)
{
Random random=new Random();
i=random.nextInt(9);
Object o=i;
ViewGroup group = (ViewGroup) findViewById(R.id.gridLayout)
myview=(View) group.findViewWithTag(o);
Log.i("Info","View tag is "+myview.getTag().toString());
isHuman=true;
}
}
In the logcat, it shows the following error:
2020-05-15 12:26:40.605 10000-10000/com.s090.tttsingleplayer E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.s090.tttsingleplayer, PID: 10000
java.lang.IllegalStateException: Could not execute method for android:onClick
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.view.View.getTag()' on a null object reference
at com.s090.tttsingleplayer.MainActivity.drop_in(MainActivity.java:133)
How can I retrieve the views using tags?
Use this line alse
ImageView counter = (ImageView) view.findViewById(R.id.counter_topLeft);
myview=(View) group.findViewWithTag(String.valueOf(i));
Here is a quote I have copied from Official Android developer site :
Tags
Unlike IDs, tags are not used to identify views. Tags are essentially
an extra piece of information that can be associated with a view. They
are most often used as a convenience to store data related to views in
the views themselves rather than by putting them in a separate
structure.
So you must not use tags to find them in runtime. My suggestion is to use an int array to store your ImageViews' IDs and use Random class to generate random indexes with that :
int[] imageviewIds={R.id.image1,R.id.image2 \* .etc*\};
Random random=new Random();
i=random.nextInt(9);
myview=(View) group.findViewById(imageIds[i]);

Why I can't enforce EditTextPreference to take just numbers?

GOAL: Use EditTextPreference to take a number input from an user.
PROBLEM: I'm using support.v7.preference and, from what I've understood, this library bring problems to creates a custom DialogPreference (HowTo use support.v7.preference with AppCompat and potential drawbacks). Anyway I tried to create a NumberPickerPreference extending DialogPreference but I got ClassCastException.
Also I found another approach: programmatically set the input type of an EditTextPreference object like below:
EditTextPreference pref = (EditTextPreference) findPreference("pref_edit_text");
if (pref != null) {
pref.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
}
No way, I always get a ClassCastException .
Last approach I tried : modify the xml
android:inputType="numberDecimal"
android:digits="0123456789"
or android:numeric="integer"
No way again, on the Dialog I get the full keyboard, so the user can write everything.
Is there a way to properly enforce EditTextReference to take just number input?
You can do it by adding android: inputType = "numberPassword" to your XML file
Then you can set your custom keyboard programmatically in your java like this:
yourEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
The next thing you could do is to create a custom class:
private class YourNumericKeyboardMethod extends PasswordTransformationMethod {
#Override
public CharSequence getTransformation(CharSequence source, View view) {
return source;
}
}
The last step is to implement it to your EditText in order to make it display only the numbers:
yourEditText.setTransformationMethod(new YourNumericKeyboardMethod());
After all this steps you should get a keyboard that has only numbers from 0 to 9 on it
Try one of these:
android:inputType="numberSigned"
android:inputType="phone"
EDIT:
android:inputType="numberDecimal|numberSigned"
android:digits="0123456789"
EDIT:
There´s this lib that is supposed to make it work, if you´re willing to try:
https://github.com/Gericop/Android-Support-Preference-V7-Fix
Reading their github page you can find examples of custom inputs and their example code that looks similar to what you are doing now:
"The sample app shows an example of setting (via XML) and querying (programmatically) the input type of the EditTextPreference:
<EditTextPreference
android:inputType="phone"
android:key="edit_text_test" />
EditTextPreference etPref = (EditTextPreference) findPreference("edit_text_test");
if (etPref != null) {
int inputType = etPref.getEditText().getInputType();
// do something with inputType
}
Try to set inputType as the number.
android:inputType="number"

Cant setId for a view programatically with AndroidStudio

I want to add view programatically to my parent view, my view is a RadioButton. I also want to set an Id for the view that I add in RadioGroup. But when I do this it gives me an error in: mRadioButton.setId(321);
Reports two types of problems:
Supplying the wrong type of resource identifier. For example, when calling Resources.getString(int id), you should be passing R.string.something, not R.drawable.something.
Passing the wrong constant to a method which expects one of a specific set of constants. For example, when calling View#setLayoutDirection, the parameter must be android.view.View.LAYOUT_DIRECTION_LTR or android.view.View.LAYOUT_DIRECTION_RTL.
I dont know why it gives me this error.
I found that one solution would be to create MyRadioButton class which extends RadioButton and there I could add a variable int MYID; and then add getters and setters for this view.
But this is a workaround, does anyone know the solution of this problem ?
Thanks!
EDIT:
Another workaround is to use setTag()
(I have used it for a horizontal view with ImageView's added dynamically and it helps me to detect which one was clicked)
If you are only supporting API 17 and higher,
you can call View.generateViewId
ImageButton mImageButton = new ImageButton(this);
mImageButton.setId(View.generateViewId());
Otherwise for apis lower than 17,
open your project's res/values/ folder
create an xml file called ids.xml
with the following content:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="imageButtonId1" type="id" />
</resources>
then in your code,
ImageButton mImageButton = new ImageButton(this);
mImageButton.setId(R.id.imageButtonId1);

Android - define groups in R.id

Is there any possibility to group the R.id?
android:id="#+id/button1"
R.id.button1
I want something like this:
android:id="#+id/main/button1"
R.id.main.button1
But that does not work.
The only way I know to group like id's like that is to set all the tags the same. Then you get reference the id's with the same tag.
Button myButton = (Button)findViewById(R.id.button1);
Button mySecondButton = (Button)findViewById(R.id.button2);
myButton.setTag("Tag1");
mySecondButton.setTag("Tag1");
This way you can still programatically reference your group.
In the R.java file, the id itself is a group, and there is no way to superclass it with something else.
As a matter of fact you can. This isn't documented, and IntelliJ does not play along nicely (Eclipse does), but you can do something like this...
layout.xml
android:id="#+id_group1/button1"
Activity.java
Button myButton = (Button)findViewById(R.id_group1.button1);

Activities loading xml layout dynamically in android

Is it possible to load an activity's xml layout file from a resource stored in the device (in a db or part of the resources) and load it dynamically when that activity is started ? The idea is to send it to the device from a web service. Thanks.
If you are trying to inflate a XML file that was not included during the build process I don't think it is currently possible. This is from the java-docs of the LayoutInflater class:
View android.view.LayoutInflater.inflate(XmlPullParser parser,
ViewGroup root, boolean attachToRoot)
Inflate a new view hierarchy from the specified XML node. Throws
InflateException if there is an error.
Important For performance reasons, view inflation relies heavily on
pre-processing of XML files that is done at build time. Therefore, it
is not currently possible to use LayoutInflater with an XmlPullParser
over a plain XML file at runtime.
As of the date of this posting, Android only contains a built-in way to inflate layout XML stored as a layout resource in the APK file. If you want to inflate similar (or different) XML from other sources, you will have to implement that yourself, perhaps by cloning some logic from the LayoutInflater class. Be warned that Android does a lot of work to optimize reading and parsing of resource files at run-time, so if you plan on loading this dynamically on your own, be prepared for it to inflate a LOT slower.
If you have to load info from a db, maybe it helps you to do a basic XML, and insert info on it, like a table or something like that. You can try something like this:
In a basic table XML:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1"
android:id="#+id/SensorInfoTableLayout">
<TableRow>
<TextView
android:text="#string/sensor_name_title"
android:padding="3dip" />
<TextView
android:text="#string/sensor_type_title"
android:padding="3dip" />
<TextView
android:text="#string/sensor_value_title"
android:padding="3dip" />
<TextView
android:text="#string/sensor_unit_title"
android:padding="3dip" />
</TableRow>
<View
android:layout_height="4dip"
android:background="#FF909090" /></TableLayout>
And the code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSensorInfo();
setContentView(R.layout.sensorinfo);
setInfoByView();
}
private void setInfoByView() {
TableLayout myTableLayout = null;
myTableLayout = (TableLayout)findViewById(R.id.SensorInfoTableLayout);
// Add row to table
myTableLayout.addView(createRow("maxRange",maxRangeType, "" + maxRange ,maxRangeUnits));
myTableLayout.addView(createRow("minDelay",minDelayType,"" + minDelay, minDelayUnits));
myTableLayout.addView(createRow("name",nameType,"" + name, nameUnits));
myTableLayout.addView(createRow("powerReq",powerReqType,"" + powerReq, powerReqUnits));
myTableLayout.addView(createRow("resolution",resolutionType,"" + resolution, resolutionUnits));
myTableLayout.addView(createRow("type",typeType,"" + type, typeUnits));
myTableLayout.addView(createRow("vendor",vendorType,"" + vendor, vendorUnits));
myTableLayout.addView(createRow("version",versionType,"" + version, versionUnits));
}
private TableRow createRow(String name, String type, String value, String unit) {
// Create new row
TableRow myTableRow = new TableRow(this);
myTableRow.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
//myTableRow.setGravity(Gravity.CENTER_HORIZONTAL);
myTableRow.setPadding(5, 5, 5, 5);
// Add name
myTableRow.addView(createTextView(name));
// Add type
myTableRow.addView(createTextView(type));
// Add value
myTableRow.addView(createTextView(value));
// Add units
myTableRow.addView(createTextView(unit));
return myTableRow;
}
In the XML, it only exists the principal bar, with name, title, value and unit. And dinamically, add rows with info and style.
Maybe it helps, it works for me.
The guys at flipkart have created an alternative for Android's LayoutInflater which accepts JSON, which can be hosted anywhere.And it also accepts optional data for data binding to native inflated views.
Sample JSON for View
{
"type": "LinearLayout",
"orientation": "vertical",
"padding": "16dp",
"children": [{
"layout_width": "200dp",
"gravity": "center",
"type": "TextView",
"text": "#{user.profile.name}"
}, {
"type": "HorizontalProgressBar",
"layout_width": "200dp",
"layout_marginTop": "8dp",
"max": 6000,
"progress": "#{user.profile.experience}"
}]
}
Sample Data JSON:
{
"user": {
"profile": {
"name": "John Doe",
"experience": 4192
}
}
}
And to dynamically Inflate: Use the ProteusView by including it in project:
ProteusView view = proteusLayoutInflater.inflate(<layout>, <data>);
container.addView(view.getAsView());
Here is an introduction by the authors themselves:
https://www.slideshare.net/mobile/KiranKumar1320/proteus-android-layout-engine
Have a look at it here:
https://github.com/flipkart-incubator/proteus/blob/master/README.md
Is it possible to load an activity's xml layout file from a resource stored in the device
Yes.
View inflatedView = View.inflate(this, R.layout.sample, null);
container.addView(inflatedView);
As #Chirag Raval said.
Is it possible to load an activity's xml layout file from a db / web service?
Don't think so. Because the layout reference (id) that you call from R.layout.XPTO is pre-compiled. You can't add values during runtime.
An middle-term option that you can use (and that I use in some apps) is set a pre-determined number o "blocks" that can be re-arranged according to the data that is sent.
Example:
Receive text, image, text + image
Load: text_block.xml then image_block.xml and then text_and_image_block.xml
It is not possible "out of the box" as explained in other answers, but the open source project ItsNat Droid makes a big effort to provide rendering of Android native XML based resources downloaded remotely (HTTP) similar to a "native Android" browser, native Android based XML instead of HTML and Beanshell scripting instead of JavaScript.
Obviously the performance is not going to be the same as local pre-compiled native resources. This is similar to comparing a web application to a desktop application, a web application is fine to most of needs but is not ok for games, in Android local/remote is very similar.

Categories

Resources