I created in google sheets a custom menu linked to a custom function that pops up a dialog window with a youtube video in it. All this i did on my PC in a browser.
I now installed google sheets on my android phone, and shared the sheet myself (using a second gmail account). I now notice that the custom menu doesn't appear in google sheets app on android.
I am wondering, does custom menus and dialogs work in google sheets on android. Am i doing something incorrect in relation to permissions -- i.e. are there any permissions i need to assign to other users for them to see and use custom menu items and related functions. If yes, how can i make the correct settings.
thank you,
Dan
i used a drop-down list/combo - populated using data validation. this is supported on mobile device. then i use the onEdit(e) event to trigger the code... the first step is to identify what's been edited (which cell) and then act accordingly... an example follows where the drop-down would contain two items "Do Task 1" and "Do Task 2"... the event is triggered when any cell on the sheet is changed... the process then identifies if the cell change is that of the drop-down (cell value set to a global variable 'FunctionsCell') if it is the drop-down that triggered the event, it then gets the value (see the 'getValue()' part of the code) and then inspects it's value and then executes the code accordingly (see '// do something part')
var FunctionsCell = "B2" // global
function onEdit(e) {
var editCell = e.range.getA1Notation()
switch (editCell) {
case FunctionsCell:
{
var functionType = SpreadsheetApp.getActiveSheet().getRange(FunctionsCell).getValue()
switch(functionType) {
case "Do Task 1": {
// do something
break
}
case "Do Task 2": {
// do something
break
}
}
}
}
}
Related
I have a refresh button on my screen which TalkBack should announce as "Refresh button, refreshes the page". It shouldn't announce "double tap to activate".
I'm using
contentDescription="refresh"
and a custom accessibility delegate.
Below is the custom accessibility delegate:
class CustomClickAccessibilityDelegate(private val clickDescription: String) : View AccessibilityDelegate {
override fun onInitialiseAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
super.onInitialiseAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, clickDescription)
info?.addAction(customClick)
}
}
With contentDescription="refresh", and the custom accessibility delegate taking "refreshes the page", announcement reads "refresh button. Double tap to refreshes the page" while I need it to announce "refresh button, refreshes the page". Is there a way to prevent TalkBack from saying "double tap to".
To achieve something close to this, you want to replace the action description of the component:
/**
* renames the "activate" portion of the announcement from "double tap to activate"
* #param descriptionResourceId: The string resource - should be translated
*
* example usage:
* val refreshButton = findViewById<Button>(R.id.refresh_button)
* refreshButton.renameClickAction(R.string.action_refresh)
*
* reference: https://developer.android.com/guide/topics/ui/accessibility/principles#understandable-actions
*/
fun Button.renameClickAction(#StringRes descriptionResourceId: Int) {
ViewCompat.replaceAccessibilityAction(
this,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
// Announcement read by TalkBack to surface this action
getText(descriptionResourceId),
null
)
}
I made it an extension function, but the documentation demonstrates that this is not necessary. It is worth noting that screen reader users who use different languages may be left out as a result.
It's worth pointing out that the "double tap to" portion is part of TalkBack, and indicates the gestures that are available to users (like if there are addition actions, state, etc) - If you want to take that away you are moving away from a consistent experience for users, and it may be difficult for them to use your app. Other portions of the documentation also infer that we should be adding more actions, not removing them
It is important to allow users of accessibility services to easily perform all user flows within your app. For example, if a user can swipe on an item in a list, this action can also be exposed to Accessibility services so users have an alternative way to complete the same user flow.
By removing the announcement for "Double tap to ..." how will folks know that there are actions available? The main WCAG regression you would be introducing is that of Success Criterion 3.2.4: Consistent navigation:
Components that have the same functionality within a set of [Activities] are identified consistently.
What makes this button so unique that users should not know that they can activate it the same way as other buttons? Perhaps I am misunderstanding the reason for removing this requirement, but from the question it seems a little odd to require it's removal.
I'm using SelectionTracker form the support-library-v28
It works great, just as expected.
Only thing I need, is to enable Select All feature (using the ToolBar)
Looking at the API, I see that there is one way to select all, but that one requires creating Iterable<Long> with all the values, meaning, create an array which hold Long values from 1 to datasource.size()
Is there any simpler way to select all the items in my datasource?
Seeing as I've recently had to set up the same sort of functionality, I thought I'd share my approach. Using the setItemsSelected(Iterable<K> keys, boolean selected) method really isn't as complex as it seems.
Yes, you will need to pass in an iterable. What I did was, loop through my data and store the index of each item as a 'long' inside of an arrayList().
EX:
yourData.forEachWithIndex { i, item ->
//Be sure to start at one, just plus one
someOtherTempArray.add(i.toLong() + 1)
}
Then I created a method that makes it easy to 'trigger' the select all functionality:
EX:
private fun startHandler(isChecked: Boolean){
val handler = android.os.Handler(Looper.getMainLooper())
val runnable = Runnable {
kotlin.run {
mTracker!!.setItemsSelected(someOtherTempArray.asIterable(),
isChecked)
}
}
handler.post(runnable)
}
The 'isChecked' parameter, which will essentially determine if we want to select all or deselect all.
Note that I am simply using my arrayList of 'keys' (for me this is just the index of the item. However, this may differ depending on how you have your ItemDetails Builder set up) and calling the Kotlin .asIterable() function to turn it into an iterable.
I have also placed the call to setItemsSelected() inside of a handler to force the selection to take place on the UI thread.
Now whenever you want to select/deselect all, you can call the startHandler(true)!
I am investigating the use of GraphStream on Android using
api 'com.github.graphstream:gs-ui-android:2.0-alpha'
api 'com.github.graphstream:gs-core:2.0-alpha'
I have managed to construct and display my Graph fine,
However I cannot see how to listen for user interactions on the nodes within my graph.
I need to display a Dialog when any node is clicked on by the user and display the specific nodes information.
I've tried setting a Listener on the org.graphstream.ui.android_viewer.AndroidViewer, however I never received any callbacks
I can drag the displayed nodes around the screen so I know there are default listeners, how to I add my own listeners though?
You can implement ViewerListener with the ViewerPipe like this :
ViewerPipe pipe = fragment.getViewer().newViewerPipe();
pipe.addAttributeSink( graph );
pipe.addViewerListener( this ); // this or any class which implements ViewerListener
pipe.pump();
You can get an exemple here : https://github.com/graphstream/gs-ui-android-test/blob/master/app/src/main/java/ui/graphstream/org/gs_ui_androidtestFull/Activity_TestArrows.java
And here to understand how ViewerPipe works : http://graphstream-project.org/doc/Tutorials/Graph-Visualisation/
I'm using a custom dynamic contentDescription for my textview, so it has been implemented in my java class and not in my xml file.
private void enableButton(TextView textView, boolean enabled) {
if (textView == null) {
return;
}
if (!enabled) {
textView.setContentDescription(textView.getResources().getString(R.string.install_disabled));
} else {
textView.setContentDescription(textView.getResources().getString(R.string.install));
}
textView.setEnabled(enabled);
}
After I'm enabling my textview to be clickable, and when talkback is enabled, focusing on my textview is announcing the state of my textview which is "disabled". Is there a way to not announce that state?
I do not want to set the accessibility to be not important because I still want my dynamic contentDescription to be recited when talkback users focus on the textview.
Suggestion:
I believe the culprit is the "setEnabled" method that is somehow triggering and announcing the state of the textview, but I'm still not able to stop it from reciting that last.
My first answer is: LEAVE IT ALONE! The "disabled" announcement tells a TalkBack user that there is a user interface control there, that under some circumstances can be interacted with, but is not currently active. Given your description, this is exactly what you have. To remove the announcement is actually going to make things WORSE from an accessibility perspective, the explanations for why this is the case are covered in WCAG 1.3.1.
Definitions:
Button = android.widget.Button
button = a user interface component that does something when you click it.
Text = a user interface component that conveys information, but is not active
Long story short, the fact that the control is ever in a state that it can be active and "not disabled" is significant information on its own, and SHOULD be shared with the user. ESPECIALLY since you're using a "TextView" to implement this. This isn't a super uncommon practice, but one of the ways TalkBack calculates roles (button, link, image, text, etc) is by looking at the Class/Type of object. So, when it sees a TextView, it is going to assume Text, unless you inform it otherwise. Now, since you have added click listeners to your TextView (or Gesture Recognizers, or whatever) TalkBack may be able to figure out that the thing you're dealing with is actually a "button", and it may not. REGARDLESS, the fact that this "button" (lower case B!) is not active is important state to share with the user, and communicates to them the fact that they can somehow enable it and come back and interact with it later. This is immensely important information! Imagine if every button/link on a WebPage looked exactly like plane text? How would you know what to interact with?
Now, I will show you the different pieces of this puzzle, as information, but I really do encourage you to leave the announcement alone. This is coming from someone who routinely speaks at Accessibility conferences on Native Android development, PLEASE LEAVE THIS ANNOUNCEMENT IN. To not do so shows a misunderstanding of how users with sight impairments want to perceive controls within your application, and the information that is important to them.
The setEnabled() function on a TextView corresponds directly with the isEnabled() property of AccessibilityNodeInfo.
In order to implement the behavior you want, you want the AccessibilityNodeInfo representation of your TextView to be different from that of the actual representation of your TextView. In order to do this you can use AccessibilityDelegate, I'm actually not sure which callback you want to use. In one of these the node is likely to be "sealed" and in one of them it might not be sealed yet. You obviously want the one where the node is not yet sealed! Regardless the general approach is:
textView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
#Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
// Let the default implementation populate the info.
super.onInitializeAccessibilityNodeInfo(host, info);
// Override this particular property
info.setEnabled(whateverYouThinkItShouldBe);
}
});
Use setClickable(false) to replace setEnabled(false) will solve this problem.
I'm new to Android and I'm building a simple application to start with. It consists of a client with three screens. In the first screen the user is prompted for an Ip to connect to a server (I use an EditText and a button). If the connection is successfully established, some data will be retrieved from the server and the client will show the data on a blank screen (I use a TextView). This would be the second screen. Then, the user could ask the server for detailed information about any data that has been retrieved from the server, which would be the third screen (I use a TextView again).
The problem is that I don't know what's the best way to go about it. I have currently one activity and one XML file containing all the components of the view (EditText, button, TextView). Until now, I've been using setVisibility(View.GONE);to hide certain components depending on the screen the user is in. (For example in the first screen I have to hide both TextViews).
One of the problems I'm facing is that when I put the phone in a horizontal position the components I had hidden show up again. I don't know if hiding views is the ideal thing to do for my purpose.
I've thought that maybe I should use more than one activity, shouldn't I?
I really appreciate any help you can give me to structure my first app.
I would definitely recommend splitting up your App into multiple Activities/Fragments. Depending on how big the logic for each screen gets you will be glad you did it later on because each Activity only has one responsibility.
Look at your mail app for example. You got your List Activity showing you all your mails and then when you select one it starts the Detail Activity showing you the content of your mail. Each Activity is only responsible for one thing which make each one easier to write and maintain.
It also simplifies your layout definitions because each one only contains the relevant parts.
Seems like this is coming up a lot. Android destroys and recreates and Activity when the configuration changes. Screen rotation is part of the orientation. In order to avoid that, the Activity is responsible for retaining state. The mechanisms given for that are the onCreate and onSaveInstanceState. In your example, you could do something like the following:
int uiPhase = 1;
#Override
void onCreate( Bundle data ) {
uiPhase = data.getInt( "uiPhase", 1 );
// inflate layout
setPhase( uiPhase );
}
// invoke the following each time your screen changes
void setPhase( int newPhase ) {
uiPhase = newPhase;
switch( uiPhase ) {
case 1: // show UI elements for first screen, hide others
break;
case 2: // show UI elements for second screen, hide others
break;
case 3: // show UI elements for third screen, hide others
break;
}
}
#Override
void onSaveInstanceState( Bundle data ) {
data.put( "uiPhase", uiPhase );
}
I didn't want to complicate the pattern above too much, but a good method for setting visibility is as follows:
phase1view.setVisibility( uiPhase == 1 ? View.VISIBLE : View.GONE );
phase2view.setVisibility( uiPhase == 2 ? View.VISIBLE : View.GONE );
phase3view.setVisibility( uiPhase == 3 ? View.VISIBLE : View.GONE );
That pulls the repetition in the setPhase method quite a bit together.
Set button visibility to GONE (button will be completely "removed" -- the buttons space will be available for another widgets) or INVISIBLE (button will became "transparent" -- its space will not be available for another widgets):
use in place of
setVisibility(View.GONE)
change to
setVisibility(View.INVISIBLE) and try