Android HTML5 video fullscreen and rotation - html

i read the article
Android WebView: handling orientation changes
and follow the tip i build a project
MainActivity
package com.example.testvideo1;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
import android.os.Build;
#SuppressLint("NewApi")
public class MainActivity extends ActionBarActivity {
private VideoEnabledWebView webView;
private VideoEnabledWebChromeClient webChromeClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set layout
setContentView(R.layout.activity_main);
// Save the web view
webView = (VideoEnabledWebView) findViewById(R.id.webView);
// Initialize the VideoEnabledWebChromeClient and set event handlers
View nonVideoLayout = findViewById(R.id.nonVideoLayout);
// Your own view,
//Your own view,
ViewGroup videoLayout = (ViewGroup) findViewById(R.id.videoLayout);
View loadingView = null; // Your own view, read class comments
webChromeClient = new VideoEnabledWebChromeClient(nonVideoLayout,
videoLayout, loadingView, webView) // See all available
// constructors...
{
// Subscribe to standard events, such as onProgressChanged()...
#Override
public void onProgressChanged(WebView view, int progress) {
// Your code...
}
};
webChromeClient
.setOnToggledFullscreen(new VideoEnabledWebChromeClient.ToggledFullscreenCallback() {
#Override
public void toggledFullscreen(boolean fullscreen) {
// Your code to handle the full-screen change, for
// example showing and hiding the title bar. Example:
if (fullscreen) {
WindowManager.LayoutParams attrs = getWindow()
.getAttributes();
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setAttributes(attrs);
if (android.os.Build.VERSION.SDK_INT >= 14) {
getWindow()
.getDecorView()
.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LOW_PROFILE);
}
} else {
WindowManager.LayoutParams attrs = getWindow()
.getAttributes();
attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setAttributes(attrs);
if (android.os.Build.VERSION.SDK_INT >= 14) {
getWindow().getDecorView()
.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_VISIBLE);
}
}
}
});
webView.setWebChromeClient(webChromeClient);
// Navigate everywhere you want, this classes have only been tested on
// YouTube's mobile site
webView.loadUrl("http://app.vlooks.cn/webchat/html5/2300/home");
}
#Override
public void onBackPressed() {
// Notify the VideoEnabledWebChromeClient, and handle it ourselves if it
// doesn't handle it
if (!webChromeClient.onBackPressed()) {
if (webView.canGoBack()) {
webView.goBack();
} else {
// Close app (presumably)
super.onBackPressed();
}
}
}
}
follow is VideoEnabledWebChromeClient.java
package com.example.testvideo1;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.webkit.WebChromeClient;
import android.widget.FrameLayout;
/**
* This class serves as a WebChromeClient to be set to a WebView, allowing it to
* play video. Video will play differently depending on target API level
* (in-line, fullscreen, or both).
*
* It has been tested with the following video classes: -
* android.widget.VideoView (typically API level <11) -
* android.webkit.HTML5VideoFullScreen$VideoSurfaceView/VideoTextureView
* (typically API level 11-18) -
* com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView
* (typically API level 19+)
*
* Important notes: - For API level 11+, android:hardwareAccelerated="true" must
* be set in the application manifest. - The invoking activity must call
* VideoEnabledWebChromeClient's onBackPressed() inside of its own
* onBackPressed(). - Tested in Android API levels 8-19. Only tested on
* http://m.youtube.com.
*
* #author Cristian Perez (http://cpr.name)
*
*/
public class VideoEnabledWebChromeClient extends WebChromeClient implements
OnPreparedListener, OnCompletionListener, OnErrorListener {
public interface ToggledFullscreenCallback {
public void toggledFullscreen(boolean fullscreen);
}
private View activityNonVideoView;
private ViewGroup activityVideoView;
private View loadingView;
private VideoEnabledWebView webView;
private boolean isVideoFullscreen; // Indicates if the video is being
// displayed using a custom view
// (typically full-screen)
private FrameLayout videoViewContainer;
private CustomViewCallback videoViewCallback;
private ToggledFullscreenCallback toggledFullscreenCallback;
/**
* Never use this constructor alone. This constructor allows this class to
* be defined as an inline inner class in which the user can override
* methods
*/
#SuppressWarnings("unused")
public VideoEnabledWebChromeClient() {
}
/**
* Builds a video enabled WebChromeClient.
*
* #param activityNonVideoView
* A View in the activity's layout that contains every other view
* that should be hidden when the video goes full-screen.
* #param activityVideoView
* A ViewGroup in the activity's layout that will display the
* video. Typically you would like this to fill the whole layout.
*/
#SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView,
ViewGroup activityVideoView) {
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = null;
this.webView = null;
this.isVideoFullscreen = false;
}
/**
* Builds a video enabled WebChromeClient.
*
* #param activityNonVideoView
* A View in the activity's layout that contains every other view
* that should be hidden when the video goes full-screen.
* #param activityVideoView
* A ViewGroup in the activity's layout that will display the
* video. Typically you would like this to fill the whole layout.
* #param loadingView
* A View to be shown while the video is loading (typically only
* used in API level <11). Must be already inflated and without a
* parent view.
*/
#SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView,
ViewGroup activityVideoView, View loadingView) {
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = loadingView;
this.webView = null;
this.isVideoFullscreen = false;
}
/**
* Builds a video enabled WebChromeClient.
*
* #param activityNonVideoView
* A View in the activity's layout that contains every other view
* that should be hidden when the video goes full-screen.
* #param activityVideoView
* A ViewGroup in the activity's layout that will display the
* video. Typically you would like this to fill the whole layout.
* #param loadingView
* A View to be shown while the video is loading (typically only
* used in API level <11). Must be already inflated and without a
* parent view.
* #param webView
* The owner VideoEnabledWebView. Passing it will enable the
* VideoEnabledWebChromeClient to detect the HTML5 video ended
* event and exit full-screen. Note: The web page must only
* contain one video tag in order for the HTML5 video ended event
* to work. This could be improved if needed (see Javascript
* code).
*/
public VideoEnabledWebChromeClient(View activityNonVideoView,
ViewGroup activityVideoView, View loadingView,
VideoEnabledWebView webView) {
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = loadingView;
this.webView = webView;
this.isVideoFullscreen = false;
}
/**
* Indicates if the video is being displayed using a custom view (typically
* full-screen)
*
* #return true it the video is being displayed using a custom view
* (typically full-screen)
*/
public boolean isVideoFullscreen() {
return isVideoFullscreen;
}
/**
* Set a callback that will be fired when the video starts or finishes
* displaying using a custom view (typically full-screen)
*
* #param callback
* A VideoEnabledWebChromeClient.ToggledFullscreenCallback
* callback
*/
public void setOnToggledFullscreen(ToggledFullscreenCallback callback) {
this.toggledFullscreenCallback = callback;
}
#Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (view instanceof FrameLayout) {
// A video wants to be shown
FrameLayout frameLayout = (FrameLayout) view;
View focusedChild = frameLayout.getFocusedChild();
// Save video related variables
this.isVideoFullscreen = true;
this.videoViewContainer = frameLayout;
this.videoViewCallback = callback;
// Hide the non-video view, add the video view, and show it
activityNonVideoView.setVisibility(View.INVISIBLE);
activityVideoView.addView(videoViewContainer, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
activityVideoView.setVisibility(View.VISIBLE);
if (focusedChild instanceof android.widget.VideoView) {
// android.widget.VideoView (typically API level <11)
android.widget.VideoView videoView = (android.widget.VideoView) focusedChild;
// Handle all the required events
videoView.setOnPreparedListener(this);
videoView.setOnCompletionListener(this);
videoView.setOnErrorListener(this);
} else {
// Other classes, including:
// - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which
// inherits from android.view.SurfaceView (typically API level
// 11-18)
// - android.webkit.HTML5VideoFullScreen$VideoTextureView, which
// inherits from android.view.TextureView (typically API level
// 11-18)
// -
// com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView,
// which inherits from android.view.SurfaceView (typically API
// level 19+)
// Handle HTML5 video ended event only if the class is a
// SurfaceView
// Test case: TextureView of Sony Xperia T API level 16 doesn't
// work fullscreen when loading the javascript below
if (webView != null
&& webView.getSettings().getJavaScriptEnabled()
&& focusedChild instanceof SurfaceView) {
// Run javascript code that detects the video end and
// notifies the Javascript interface
String js = "javascript:";
js += "var _ytrp_html5_video_last;";
js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];";
js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {";
{
js += "_ytrp_html5_video_last = _ytrp_html5_video;";
js += "function _ytrp_html5_video_ended() {";
{
js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must
// match
// Javascript
// interface
// name
// and
// method
// of
// VideoEnableWebView
}
js += "}";
js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);";
}
js += "}";
webView.loadUrl(js);
}
}
// Notify full-screen change
if (toggledFullscreenCallback != null) {
toggledFullscreenCallback.toggledFullscreen(true);
}
}
}
#Override
#SuppressWarnings("deprecation")
public void onShowCustomView(View view, int requestedOrientation,
CustomViewCallback callback) // Available in API level 14+,
// deprecated in API level 18+
{
onShowCustomView(view, callback);
}
#Override
public void onHideCustomView() {
// This method should be manually called on video end in all cases
// because it's not always called automatically.
// This method must be manually called on back key press (from this
// class' onBackPressed() method).
if (isVideoFullscreen) {
// Hide the video view, remove it, and show the non-video view
activityVideoView.setVisibility(View.INVISIBLE);
activityVideoView.removeView(videoViewContainer);
activityNonVideoView.setVisibility(View.VISIBLE);
// Call back (only in API level <19, because in API level 19+ with
// chromium webview it crashes)
if (videoViewCallback != null
&& !videoViewCallback.getClass().getName()
.contains(".chromium.")) {
videoViewCallback.onCustomViewHidden();
}
// Reset video related variables
isVideoFullscreen = false;
videoViewContainer = null;
videoViewCallback = null;
// Notify full-screen change
if (toggledFullscreenCallback != null) {
toggledFullscreenCallback.toggledFullscreen(false);
}
}
}
#Override
public View getVideoLoadingProgressView() // Video will start loading, only
// called in the case of
// VideoView (typically API
// level 10-)
{
if (loadingView == null) {
return super.getVideoLoadingProgressView();
} else {
loadingView.setVisibility(View.VISIBLE);
return loadingView;
}
}
#Override
public void onPrepared(MediaPlayer mp) // Video will start playing, only
// called in the case of
// android.widget.VideoView
// (typically API level <11)
{
if (loadingView != null) {
loadingView.setVisibility(View.GONE);
}
}
#Override
public void onCompletion(MediaPlayer mp) // Video finished playing, only
// called in the case of
// android.widget.VideoView
// (typically API level <11)
{
onHideCustomView();
}
#Override
public boolean onError(MediaPlayer mp, int what, int extra) // Error while
// playing
// video, only
// called in the
// case of
// android.widget.VideoView
// (typically
// API level
// <11)
{
return false; // By returning false, onCompletion() will be called
}
/**
* Notifies the class that the back key has been pressed by the user. This
* must be called from the Activity's onBackPressed(), and if it returns
* false, the activity itself should handle it. Otherwise don't do anything.
*
* #return Returns true if the event was handled, and false if was not
* (video view is not visible)
*/
public boolean onBackPressed() {
if (isVideoFullscreen) {
onHideCustomView();
return true;
} else {
return false;
}
}
}
and VideoEnabledWebView.java
package com.example.testvideo1;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import java.util.Map;
/**
* This class serves as a WebView to be used in conjunction with a
* VideoEnabledWebChromeClient. It makes possible: - To detect the HTML5 video
* ended event so that the VideoEnabledWebChromeClient can exit full-screen.
*
* Important notes: - Javascript is enabled by default and must not be disabled
* with getSettings().setJavaScriptEnabled(false). - setWebChromeClient() must
* be called before any loadData(), loadDataWithBaseURL() or loadUrl() method.
*
* #author Cristian Perez (http://cpr.name)
*
*/
public class VideoEnabledWebView extends WebView {
public class JavascriptInterface {
#android.webkit.JavascriptInterface
public void notifyVideoEnd() // Must match Javascript interface method
// of VideoEnabledWebChromeClient
{
// This code is not executed in the UI thread, so we must force that
// to happen
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
if (videoEnabledWebChromeClient != null) {
videoEnabledWebChromeClient.onHideCustomView();
}
}
});
}
}
private VideoEnabledWebChromeClient videoEnabledWebChromeClient;
private boolean addedJavascriptInterface;
public VideoEnabledWebView(Context context) {
super(context);
addedJavascriptInterface = false;
}
#SuppressWarnings("unused")
public VideoEnabledWebView(Context context, AttributeSet attrs) {
super(context, attrs);
addedJavascriptInterface = false;
}
#SuppressWarnings("unused")
public VideoEnabledWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
addedJavascriptInterface = false;
}
/**
* Indicates if the video is being displayed using a custom view (typically
* full-screen)
*
* #return true it the video is being displayed using a custom view
* (typically full-screen)
*/
public boolean isVideoFullscreen() {
return videoEnabledWebChromeClient != null
&& videoEnabledWebChromeClient.isVideoFullscreen();
}
/**
* Pass only a VideoEnabledWebChromeClient instance.
*/
#Override
#SuppressLint("SetJavaScriptEnabled")
public void setWebChromeClient(WebChromeClient client) {
getSettings().setJavaScriptEnabled(true);
if (client instanceof VideoEnabledWebChromeClient) {
this.videoEnabledWebChromeClient = (VideoEnabledWebChromeClient) client;
}
super.setWebChromeClient(client);
}
#Override
public void loadData(String data, String mimeType, String encoding) {
addJavascriptInterface();
super.loadData(data, mimeType, encoding);
}
#Override
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl) {
addJavascriptInterface();
super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
#Override
public void loadUrl(String url) {
addJavascriptInterface();
super.loadUrl(url);
}
#Override
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
addJavascriptInterface();
super.loadUrl(url, additionalHttpHeaders);
}
private void addJavascriptInterface() {
if (!addedJavascriptInterface) {
// Add javascript interface to be called when the video ends (must
// be done before page load)
addJavascriptInterface(new JavascriptInterface(),
"_VideoEnabledWebView"); // Must match Javascript interface
// name of
// VideoEnabledWebChromeClient
addedJavascriptInterface = true;
}
}
}
and the layout file
<!-- View that will be hidden when video goes fullscreen -->
<RelativeLayout
android:id="#+id/nonVideoLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.VideoEnabledWebView
android:id="#+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<!-- View where the video will be shown when video goes fullscreen -->
<RelativeLayout
android:id="#+id/videoLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- View that will be shown while the fullscreen video loads (maybe include a spinner and a "Loading..." message) -->
<View
android:id="#+id/videoLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="invisible" />
</RelativeLayout>
the project can't work, and i want when fullscreen , let the program go LandScape mode.
but i failed.
thanks.......

I've been facing the same issue.
Although this question is quite old, I have come to a simple solution which I could not find on stack overflow.
Just enforce Landscape when going full screen:
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
And revert this, when going back.
In context:
public class BaseWebChromeClient extends WebChromeClient
{
#Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)
{
[...]
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
[...]
#Override
public void onHideCustomView(View view, WebChromeClient.CustomViewCallback callback)
{
[...]
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// use SCREEN_ORIENTATION_SENSOR, if you don't to enforce portrait mode.
[...]
}
}

Related

How to call Update function's code by button click

How can I call the codes, inside the Update function using a public method?
What I need to archive is, calling Update using another function.
So that Update triggers using that other method.
One more thing, code should run only when a button long press.
Many Thanks four help
using UnityEngine;
using System.Collections;
public class CarController : MonoBehaviour
{
public float speed = 1500f;
public float rotationSpeed = 15f;
public WheelJoint2D backWheel;
public WheelJoint2D frontWheel;
public Rigidbody2D rb;
private float movement = 0f;
private float rotation = 0f;
void Update()
{
rotation = Input.GetAxisRaw("Horizontal");
movement = -Input.GetAxisRaw("Vertical") * speed;
}
void FixedUpdate()
{
if (movement == 0f)
{
backWheel.useMotor = false;
frontWheel.useMotor = false;
}
else
{
backWheel.useMotor = true;
frontWheel.useMotor = true;
JointMotor2D motor = new JointMotor2D { motorSpeed = movement, maxMotorTorque = 10000 };
backWheel.motor = motor;
frontWheel.motor = motor;
}
rb.AddTorque(-rotation * rotationSpeed * Time.fixedDeltaTime);
}
//public void Rotate()
//{
// rotate = true;
// print("aa");
//}
//public void Move()
//{
// rotation = Input.GetAxisRaw("Horizontal");
// movement = -Input.GetAxisRaw("Vertical") * speed;
//}
}
Those are actually two questions.
1. A long press button
The Unity UI.Button hasn't per se a method for a long press but you can use the IPointerXHandler interfaces for implementing that on your own:
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// RequireComponent makes sure there is Button on the GameObject
[RequireComponent(typeof(Button))]
public class LongPressButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
private void Awake()
{
ResetButton();
}
// set the long press duration in the editor (in seconds)
public float LongPressDuration = 0.5f;
// Here you reference method just like in onClick
public UnityEvent onLongPress;
private float _timer;
private bool _isPressed;
private bool _pressInvoked;
private void Update()
{
// prevent multiple calls if button stays pressed
if (_pressInvoked) return;
// if button is not pressed do nothing
if (!_isPressed) return;
// reduce the timer by the time passed since last frame
_timer -= Time.deltaTime;
// if timeout not reached do nothing
if (!(_timer <= 0)) return;
// Invoke the onLongPress event -> call all referenced callback methods
onLongPress.Invoke();
_pressInvoked = true;
}
// reset all flags and timer
private void ResetButton()
{
_isPressed = false;
_timer = LongPressDuration;
_pressInvoked = false;
}
/* IPointer Callbacks */
// enable the timer
public void OnPointerDown(PointerEventData eventData)
{
_isPressed = true;
}
// reset if button is released before timeout
public void OnPointerUp(PointerEventData eventData)
{
ResetButton()
}
// reset if cursor leaves button before timeout
public void OnPointerExit(PointerEventData eventData)
{
ResetButton();
}
}
This script has to be placed next to the Button component.
You don't reference the callback method(s) in the Button's
onClick but instead in this LongPressButton's onLongPress
and don't forget to adjust LongPressDuration also in the inspector.
Example
2. Calling CarController's Update
I don't know why you want this (I guess you disabled the component but want to call Update anyway)
Solution A
In order to be able to reference that method in the Inspector there are a few options:
Simply make your Update method public
public void Update()
{
rotation = Input.GetAxisRaw("Horizontal");
movement = -Input.GetAxisRaw("Vertical") * speed;
}
wrap the content of Update in another public method and use that one instead:
private void Update()
{
DoUpdateStuff();
}
public void DoUpdateStuff()
{
rotation = Input.GetAxisRaw("Horizontal");
movement = -Input.GetAxisRaw("Vertical") * speed;
}
the other way round (how you requested it) - call Update from another public method:
private void Update()
{
rotation = Input.GetAxisRaw("Horizontal");
movement = -Input.GetAxisRaw("Vertical") * speed;
}
public void DoUpdateStuff()
{
Update();
}
So all that's left is referencing the CarController's Update or DoUpdateStuff method in the LongPressButton's onLongPress event.
Solution B
Alternatively you could add that callback directly on runtime without referencing anything nor making the callback method public so you could directly use private void Update without a wrapper method.
Drawback: For this method you somehow have to get the reference to that LongPressButton in your CarController script instead
public class CarController : MonoBehaviour
{
// Somehow get the reference for this either by referencing it or finding it on runtime etc
// I will asume this is already set
public LongPressButton longPressButton;
private void Awake()
{
// make sure listener is only added once
longPressButton.onLongPress.RemoveListener(Update);
longPressButton.onLongPress.AddListener(Update);
}
private void Update()
{
rotation = Input.GetAxisRaw("Horizontal");
movement = -Input.GetAxisRaw("Vertical") * speed;
}
private void OnDestroy()
{
// clean up the listener
longPressButton.onLongPress.RemoveListener(Update);
}
//...
}
Thanks for your descriptive reply. What I did was change my script to following and added Event Trigger component. Then call the public functions accordingly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CarMve : MonoBehaviour
{
bool move = false;
bool moveR = false;
public Rigidbody2D rb;
public float speed = 20f;
public float rotationSpeed = 2f;
private void FixedUpdate()
{
if (move == true)
{
rb.AddForce(transform.right * speed * Time.fixedDeltaTime * 100f, ForceMode2D.Force);
}
if (moveR == true)
{
rb.AddForce(transform.right *- speed * Time.fixedDeltaTime * 100f, ForceMode2D.Force);
}
}
/* Will be used on the UI Button */
public void MoveCar(bool _move)
{
move = _move;
}
public void MoveCarR(bool _moveR)
{
moveR = _moveR;
}
}

Android 5.x SwitchPreference is not behaving the same as in Android 4.x

I have a code using SwitchPreference that used to work with Android 4.x however it no longer works since I updated my device to Android 5.0.1.
I have a simple SwitchPreference which displays a title on the left and an ON/OFF switch on the right.
<SwitchPreference
android:key="myPref"
android:selectable="true"
android:title="Title"
android:fragment="com.myApp.DeviceMonitorPrefsActivity"
android:switchTextOn="ON"
android:switchTextOff="OFF"/>
On the PreferenceActivity, I overrode onPreferenceTreeClick() to perform an Action (launching a setup Activity in my case) when I click on the title of this SwitchPreference control.
#Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
{
if(preference instanceof SwitchPreference){
// My Action
}
}
With Android 4.4.4, this Action used to be executed only when I pressed to the left of this control (title), but not when I changed the switch state.
Now with Android 5.0.1, the onPreferenceTreeClick() is called even when I change the switch state, and I didn't find a way to differentiate the two cases.
Is it a bug in Android 5.0.1 or is there a way to make this work cleanly?
This workaround found here seems to work : https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=172425
Here's my implementation that work on my case :
public class MySwitchPreference extends SwitchPreference {
/**
* Construct a new SwitchPreference with the given style options.
*
* #param context The Context that will style this preference
* #param attrs Style attributes that differ from the default
* #param defStyle Theme attribute defining the default style options
*/
public MySwitchPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Construct a new SwitchPreference with the given style options.
*
* #param context The Context that will style this preference
* #param attrs Style attributes that differ from the default
*/
public MySwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Construct a new SwitchPreference with default style options.
*
* #param context The Context that will style this preference
*/
public MySwitchPreference(Context context) {
super(context, null);
}
#Override
protected void onBindView(View view) {
ViewGroup viewGroup= (ViewGroup)view;
setSwitchClickable(viewGroup);
super.onBindView(view);
}
private void setSwitchClickable(ViewGroup viewGroup) {
if (null == viewGroup) {
return;
}
int count = viewGroup.getChildCount();
for(int n = 0; n < count; ++n) {
View childView = viewGroup.getChildAt(n);
if(childView instanceof Switch) {
final Switch switchView = (Switch) childView;
switchView.setClickable(true);
return;
} else if (childView instanceof ViewGroup){
ViewGroup childGroup = (ViewGroup)childView;
setSwitchClickable(childGroup);
}
}
}
Then you just have to use your own "MySwitchPreference" into SwitchPreference directly.

Transition gives flicker when splash screen calls "finish()" after startActivity(intent, options.toBundle());

I am creating an android app (lollipop version). App shows large logo in middle of splash screen. Login screen contains a small sized logo at top. I use ActivityOptions.makeSceneTransitionAnimation() to set animation from large logo of splash screen to small logo of login screen.
Splash screen launches to launch app. After delay of few milisec , splash screen creates intent for login screen. Also set transition. Then it starts login activity. It begins transition animation of logo. And shows login screen successfully. Everything is working well and animation is smooth up to this point.
Then I added "finish();" in splash screen so that back button on login screen do not loads splash screen. Now transition was giving flicker.
I tried following approach but still flicker is there.
used "finishAfterTransition();" instead of "finish();"
added FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_NEW_TASK flags to intent (this leads to even wired behavior)
Override onBackPressed() in login screen. and added
finish();
android.os.Process.killProcess(android.os.Process.myPid()); - This terminates app but restarts it again.
Here, I'm pasting the code of splashScreen. This code is working but gives flicker while transition. gotoLoginScreen() method at the end of class is the place which loads login activity. The login screen is basic activity template from android studio. Using com.android.support:appcompat-v7:21.0.2 library to support lower devices.
public class SplashActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
}
#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_splash, 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
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
ImageView imageView_logo;
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_splash, container, false);
imageView_logo = (ImageView) rootView.findViewById(R.id.imageview_logo);
imageView_logo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startTimer();
}
});
startTimer();
return rootView;
}
private void startTimer() {
new CountDownTimer(1000, 1000) {
#Override
public void onTick(long l) {
}
public void onFinish() {
launchNextActivity();
}
}.start();
}
/**
* base on session continuity the next activity will be decided and
* launches next activity
*/
private void launchNextActivity() {
if (isSessionContinue()) {
goToHomeScreen();
} else {
goToLoginScreen();
}
}
/**
* checks current user ID, null indicate terminated session
*
* #return true if session is continued and false of session is terminated
*/
private boolean isSessionContinue() {
return false;
}
/**
* directly leads to home screen
*/
private void goToHomeScreen() {
//code to start home screen by skipping login
}
/**
* leads to login screen.
*/
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
**private void goToLoginScreen() {
Intent loginIntent = new Intent(getActivity(), LoginActivity.class);
if (android.os.Build.VERSION.SDK_INT >= 21) {
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(getActivity(), imageView_logo, getString(R.string.transition_logo));
getActivity().startActivity(loginIntent, options.toBundle());
getActivity().finishAfterTransition();
} else {
startActivity(loginIntent);
getActivity().finish();
}
}**
}
}
Is there any way to avoid flicker? It is a stain on beauty.
add this to your activity:
private boolean shouldFinish = false;
#Override
public void onStop() {
super.onStop();
if (shouldFinish) {
getActivity().finish();
}
}
and change this:
private void goToLoginScreen() {
shouldFinish = true;
Intent loginIntent = new Intent(getActivity(), LoginActivity.class);
if (android.os.Build.VERSION.SDK_INT >= 21) {
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(getActivity(), imageView_logo, getString(R.string.transition_logo));
getActivity().startActivity(loginIntent, options.toBundle());
} else {
startActivity(loginIntent);
}
}

What is the best way to trigger a combo-box cell editor by typing in a JTable cell?

In other words, I want JTable to drop-down a combo-box whenever user types in a cell that has a JComboBox (or any other JComboBox-based cell-editor) editor associated to it.
Basically, you have to install an appropriate listener on the combo and open the popup explicitly. First candidate for "appropriate" is an AncestorListener, which invokes showing the popup in its ancestorAdded method.
Unfortunately that doesn't seem to be the whole story: works if the table's surrenderFocus property is false. If it is true works only for not-editable combos. After some digging, the reason for the not-working part turns out to be an internal focustransfer (from the combo to the textfield) after the popup is opened by the ancestorListener. In that case, we need a second listener which opens the popup once the editor's editingComponent got the focus permanently.
Multiple listeners routinely step onto each other's feet, so best to not install both permanently but do it on each call to getEditorComp, and let them uninstall themselves once they showed the popup. Below is a working example of how-to do it, just beware: it's not formally tested!
public static class DefaultCellEditorX extends DefaultCellEditor {
private AncestorListener ancestorListener;
private PropertyChangeListener focusPropertyListener;
public DefaultCellEditorX(JComboBox comboBox) {
super(comboBox);
}
/**
* Overridden to install an appriate listener which opens the
* popup when actually starting an edit.
*
* #inherited <p>
*/
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
super.getTableCellEditorComponent(table, value, isSelected, row, column);
installListener(table);
return getComponent();
}
/**
* Shows popup.
*/
protected void showPopup() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
getComponent().setPopupVisible(true);
}
});
}
/**
* Dynamically install self-uninstalling listener, depending on JComboBox
* and JTable state.
* #param table
*/
private void installListener(JTable table) {
if (getComponent().isEditable() && table.getSurrendersFocusOnKeystroke()) {
installKeyboardFocusListener();
} else {
installAncestorListener();
}
}
private void installAncestorListener() {
if (ancestorListener == null) {
ancestorListener = new AncestorListener() {
#Override
public void ancestorAdded(AncestorEvent event) {
getComponent().removeAncestorListener(ancestorListener);
showPopup();
}
#Override
public void ancestorRemoved(AncestorEvent event) {
}
#Override
public void ancestorMoved(AncestorEvent event) {
}
};
}
getComponent().addAncestorListener(ancestorListener);
}
private void installKeyboardFocusListener() {
if (focusPropertyListener == null) {
focusPropertyListener = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
LOG.info("property: " + evt.getPropertyName());
if (focusManager().getPermanentFocusOwner() !=
getComponent().getEditor().getEditorComponent()) return;
focusManager()
.removePropertyChangeListener("permanentFocusOwner", focusPropertyListener);
showPopup();
}
};
}
focusManager().addPropertyChangeListener("permanentFocusOwner", focusPropertyListener);
}
/**
* Convience for less typing.
* #return
*/
protected KeyboardFocusManager focusManager() {
return KeyboardFocusManager.getCurrentKeyboardFocusManager();
}
/**
* Convenience for type cast.
* #inherited <p>
*/
#Override
public JComboBox getComponent() {
return (JComboBox) super.getComponent();
}
}
JTable table = new JTable(data, columns);
table.putClientProperty("terminateEditOnFocusLost", true);
JScrollPane scrollPane = new JScrollPane(table);
final JXComboBox editorComboBox = new JXComboBox(array);
editorComboBox.addAncestorListener(new AncestorListener() {
public void ancestorAdded(AncestorEvent event) {
//make sure combobox handles key events
editorComboBox.requestFocusInWindow();
}
public void ancestorMoved(AncestorEvent event) {}
public void ancestorRemoved(AncestorEvent event) {}
});
AutoCompleteDecorator.decorate(editorComboBox);
TableColumn column = table.getColumnModel().getColumn(0);
column.setCellEditor(new ComboBoxCellEditor(editorComboBox));

Need the height of an invalidated Swing component

The basic setup is this: I have a vertical JSplitPane that I want to have a fixed-size bottom component and a resizing top component, which I accomplished by calling setResizeWeight(1.0). In this application there is a button to restore the "default" window configuration. The default height of the window is the desktop height, and the default divider location is 100 pixels from the bottom of the split pane.
To set the divider location to 100px, I take the JSplitPane height - 100. The problem is, just before this I resize the JFrame, and since the code is in a button callback, the JSplitPane has been invalidated but not yet resized. So the divider location is set incorrectly.
Here is a SSCCE. Click the button twice to see the problem. The first click will resize the window, but the divider location remains the same (relative to the bottom of the window). The second click properly moves the divider, since the window size didn't change.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction("Resize to Default") {
#Override
public void actionPerformed(ActionEvent e) {
restoreDefaults();
}
}),BorderLayout.PAGE_END);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
}
I have thought of a few ways I might get around this, but they all seem sort of hackish. So far the best idea I've had has been to call f.validate() in between setting the frame size and setting the divider location, but I'm concerned there might be side effects to forcing validation early.
The other option I thought of is to use EventQueue.invokeLater() to put the call to set the divider location at the end of the event queue. But that seems risky to me - I'm assuming the JSplitPane will have been validated at that point, and I'm concerned that may be a faulty assumption to make.
Is there a better way?
Took a while (probably due to being early morning here :-) to understand the problem, so just to make sure I got it:
the size of the bottom component can be whatever the user decides at all times
when resizing the frame all height change should happen to the top component
there's an option to restore to default sizes, independent of any setting before
"default" means the bottom component must have a fixed height of xx
If so, the solution is to separate the frame resizing from the sizing the bottom component. Your second option is dead on: resize the frame and wrap the bottom comp resize into a invokeLater (EventQueue or SwingUtilities, doesn't matter).
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
sp.setDividerLocation(sp.getSize().height - 100);
}
});
}
That's guaranteed to work as expected, because the invokeLater puts the request as last after all already queued events:
/**
* Causes <i>doRun.run()</i> to be executed asynchronously on the
* AWT event dispatching thread. This will happen after all
* pending AWT events have been processed. [...]
* If invokeLater is called from the event dispatching thread --
* for example, from a JButton's ActionListener -- the <i>doRun.run()</i> will
* still be deferred until all pending events have been processed.
You could create a custom action class that handles the button click and the resize event. This approach would look like this:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
CustomListener resizeViaButtonListener = new CustomListener("Resize to Default");
f.getContentPane().add(new JButton(resizeViaButtonListener), BorderLayout.PAGE_END);
f.addComponentListener(resizeViaButtonListener);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
class CustomListener extends AbstractAction implements ComponentListener {
CustomListener(String actionDescription) {
super(actionDescription);
}
private boolean resizedViaButtonClick = false;
#Override
public void actionPerformed(ActionEvent arg0) {
resizedViaButtonClick = true;
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100);
// you need this also here because if the component is not resized when clicking the button
// it is possible that the divider location must be changed. This happens when the user clicks
// the button after changing the divider but not resizing the frame.
}
#Override
public void componentResized(ComponentEvent e) {
if ( resizedViaButtonClick ) {
resizedViaButtonClick = false;
sp.setDividerLocation(sp.getSize().height - 100);
}
}
#Override
public void componentHidden(ComponentEvent e) { /* do nothing */ }
#Override
public void componentMoved(ComponentEvent e) { /* do nothing */ }
#Override
public void componentShown(ComponentEvent e) { /* do nothing */ }
}
}
This way the code that is responsible for handling the logical task of setting the standard size will be in one single and easy to understand class.
nothing complicated, basic Swing Rules
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SSCCE sSCCE = new SSCCE();
}
});
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction(
"Resize to Default") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(sp.getLastDividerLocation());
restoreDefaults();
}
}), BorderLayout.PAGE_END);
f.setPreferredSize(new Dimension(400, 300));
f.pack();
f.setVisible(true);
}
void restoreDefaults() {
//EventQueue.invokeLater(new Runnable() {
// #Override
// public void run() {
f.setPreferredSize(new Dimension(f.getWidth(),
getDesktopRect(f.getGraphicsConfiguration()).height));
f.pack();
sp.setDividerLocation(sp.getSize().height - 100);
// Does not work on first button press
// }
//});
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top,
size.width - (insets.left + insets.right),
size.height - (insets.top + insets.bottom));
}
}
but I think pack() may be better than validate()
I generally try to avoid invoking setPreferredSize() on any component. I would rather let the layout manager do its job. In this case this would mean setting the size of the frame and let the BorderLayout take all the available space.
void restoreDefaults() {
// f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
f.setSize(f.getWidth(), bounds.height);
f.validate();
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}