Android added notch support on API 28, but how to handle it on devices running API 27 (Honor 10, Huawei P20, etc.) ?
I was trying to use DisplayCutoutCompat but I was not able to create an instance of it since documentation does not really point out how create one.
How to create the constructor parameter values: Rect safeInsets, List<Rect> boundingRects?
I also looked into the source code of the constructor, which is a bit confusing to me:
public DisplayCutoutCompat(Rect safeInsets, List<Rect> boundingRects) {
this(SDK_INT >= 28 ? new DisplayCutout(safeInsets, boundingRects) : null);
}
This will always return null on devices running API < 28.
Thank you in advance.
Google provided notch related APIs in Android P. Devices with notch and API version lower than P implemented their own notch APIs.You can consult the APIs from device specified documentation.
Also I did not see creation of DisplayCutoutCompat instance in official documentation, but you can create DisplayCutout as follow:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
DisplayCutout displayCutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
}
So you want to handle notch(display cutout) in android API lower than 28. That's horrible because different manufactures has different implementations. Nevertheless, all use Java reflection to get notch information. Factory design pattern should be used here.
interface ICutout {
public boolean hasCutout();
public Rect[] getCutout();
}
Huawei display cutout
private static class HuaweiCutout implements ICutout {
private Context context;
public HuaweiCutout(#NonNull Context context) {
this.context = context;
}
#Override
public boolean hasCutout() {
try {
ClassLoader classLoader = context.getClassLoader();
Class class_HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method method_hasNotchInScreen = class_HwNotchSizeUtil.getMethod("hasNotchInScreen");
return (boolean) method_hasNotchInScreen.invoke(class_HwNotchSizeUtil);
} catch (Exception e) {
}
return false;
}
#Override
public Rect[] getCutout() {
try {
ClassLoader classLoader = context.getClassLoader();
Class class_HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method method_getNotchSize = class_HwNotchSizeUtil.getMethod("getNotchSize");
int[] size = (int[]) method_getNotchSize.invoke(class_HwNotchSizeUtil);
int notchWidth = size[0];
int notchHeight = size[1];
int screenWidth = DeviceUtil.getScreenWidth(context);
int x = (screenWidth - notchWidth) >> 1;
int y = 0;
Rect rect = new Rect(x, y, x + notchWidth, y + notchHeight);
return new Rect[] {rect};
} catch (Exception e) {
}
return new Rect[0];
}}
Oppo display cutout
private static class OppoCutout implements ICutout {
private Context context;
public OppoCutout(#NonNull Context context) {
this.context = context;
}
#Override
public boolean hasCutout() {
String CutoutFeature = "com.oppo.feature.screen.heteromorphism";
return context.getPackageManager().hasSystemFeature(CutoutFeature);
}
#Override
public Rect[] getCutout() {
String value = SystemProperties.getProperty("ro.oppo.screen.heteromorphism");
String[] texts = value.split("[,:]");
int[] values = new int[texts.length];
try {
for(int i = 0; i < texts.length; ++i)
values[i] = Integer.parseInt(texts[i]);
} catch(NumberFormatException e) {
values = null;
}
if(values != null && values.length == 4) {
Rect rect = new Rect();
rect.left = values[0];
rect.top = values[1];
rect.right = values[2];
rect.bottom = values[3];
return new Rect[] {rect};
}
return new Rect[0];
}}
Vivo display cutout
private static class VivoCutout implements ICutout {
private Context context;
public VivoCutout(#NonNull Context context) {
this.context = context;
}
#Override
public boolean hasCutout() {
try {
ClassLoader clazz = context.getClassLoader();
Class ftFeature = clazz.loadClass("android.util.FtFeature");
Method[] methods = ftFeature.getDeclaredMethods();
for(Method method: methods) {
if (method.getName().equalsIgnoreCase("isFeatureSupport")) {
int NOTCH_IN_SCREEN = 0x00000020; // 表示是否有凹槽
int ROUNDED_IN_SCREEN = 0x00000008; // 表示是否有圆角
return (boolean) method.invoke(ftFeature, NOTCH_IN_SCREEN);
}
}
} catch (Exception e) {
}
return false;
}
#Override
public Rect[] getCutout() {
// throw new RuntimeException(); // not implemented yet.
return new Rect[0];
}}
Xiaomi display cutout of Android Oreo, of Android Pie
private static class XiaomiCutout implements ICutout {
private Context context;
public XiaomiCutout(#NonNull Context context) {
this.context = context;
}
#Override
public boolean hasCutout() {
// `getprop ro.miui.notch` output 1 if it's a notch screen.
String text = SystemProperties.getProperty("ro.miui.notch");
return text.equals("1");
}
#Override
public Rect[] getCutout() {
Resources res = context.getResources();
int widthResId = res.getIdentifier("notch_width", "dimen", "android");
int heightResId = res.getIdentifier("notch_height", "dimen", "android");
if(widthResId > 0 && heightResId > 0) {
int notchWidth = res.getDimensionPixelSize(widthResId);
int notchHeight = res.getDimensionPixelSize(heightResId);
// one notch in screen top
int screenWidth = DeviceUtil.getScreenSize(context).getWidth();
int left = (screenWidth - notchWidth) >> 1;
int right = left + notchWidth;
int top = 0;
int bottom = notchHeight;
Rect rect = new Rect(left, top, right, bottom);
return new Rect[] {rect};
}
return new Rect[0];
}}
For SystemProperties class, you can read this thread, actually it calls adb shell getprop <property_name>.
In case some manufactures' not coming up with a getNotchHeight() method, you can just use the status bar's height. Android has guarantee that notch height is at most the status bar height.
public static int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
Resources res = context.getResources();
int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = res.getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
For Android Pie and above (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P), you can use system's API to get notch information. Note the window must be attachedActivity#onAttachedToWindow or you will get null DisplayCutout.
DisplayCutout displayCutout = activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
I had similar issue, and had to use reflection to access what I need.
My problem was that I had some calculations depending on screen size and while not accessing the notch space, the calculations were wrong and code didn't work well.
public static final String CLASS_DISPLAY_CUTOUT = "android.view.DisplayCutout";
public static final String METHOD_GET_DISPLAY_CUTOUT = "getDisplayCutout";
public static final String FIELD_GET_SAFE_INSET_TOP = "getSafeInsetTop";
public static final String FIELD_GET_SAFE_INSET_LEFT = "getSafeInsetLeft";
public static final String FIELD_GET_SAFE_INSET_RIGHT = "getSafeInsetRight";
public static final String FIELD_GET_SAFE_INSET_BOTTOM = "getSafeInsetBottom";
try {
WindowInsets windowInsets = activity.getWindow().getDecorView().getRootWindowInsets();
if (windowInsets == null) {
return;
}
Method method = WindowInsets.class.getMethod(METHOD_GET_DISPLAY_CUTOUT);
Object displayCutout = method.invoke(windowInsets);
if (displayCutout == null) {
return;
}
Class clz = Class.forName(CLASS_DISPLAY_CUTOUT);
int top = (int) clz.getMethod(FIELD_GET_SAFE_INSET_TOP).invoke(displayCutout);
int left = (int) clz.getMethod(FIELD_GET_SAFE_INSET_LEFT).invoke(displayCutout);
int right = (int) clz.getMethod(FIELD_GET_SAFE_INSET_RIGHT).invoke(displayCutout);
int bottom = (int) clz.getMethod(FIELD_GET_SAFE_INSET_BOTTOM).invoke(displayCutout);
Rect rect = new Rect(left, top, right, bottom);
} catch (Exception e) {
Log.e(TAG, "Error when getting display cutout size");
}
To handle API lower then 28 WindowInsetsCompat can be used. Kotlin example:
WindowInsetsCompat.toWindowInsetsCompat(window.decorView.rootWindowInsets).displayCutout
Related
Im currently working on a little mobile game in which you throw spears at targets, and depending on how many spears you have left at the end of the level you get between one and three Runes (similar to the Star scoring sytem in Angry Birds). The plan is to save the runes and use them to unlock different spear variants in the Main Menu.
At the moment you can see how many runes you got after completeing the level, but the values are not yet being saved.
My question is, how would I go about saving the Runes after each Level? Any ideas? You can see the part of the script Im using for that below. Thanks in advance.
public int totalRunes = 0;
public GameObject rune1;
public GameObject rune2;
public GameObject rune3;
public void RuneCollection()
{
if (currentSpears >= 2)
{
Debug.Log("3 runes collected!");
rune3.SetActive(true);
totalRunes = 3;
}
else if (currentSpears >= 1)
{
Debug.Log("2 runes collected!");
rune2.SetActive(true);
totalRunes = 2;
}
else
{
Debug.Log("1 rune collected!");
rune1.SetActive(true);
totalRunes = 1;
}
}
I would use Unitys PlayerPrefs System.
Example:
Setting Score:
public Text score;
int currentscore = 0;
Increasing Score:
currentscore += 1;
score.text = currentscore.ToString();
Setting HighScore and Loading Menu:
GameManager.SetHighScore(currentscore);
SceneManager.LoadScene("Menu");
Functions to Load/Change PlayerPrefs:
void Start()
{
highscore.text = PlayerPrefs.GetInt("HighScore", 0).ToString();
}
public static void SetHighScore(int score)
{
if (score > PlayerPrefs.GetInt("HighScore", 0))
{
PlayerPrefs.SetInt("HighScore", score);
}
}
Your Code adapted to PlayerPrefs:
public int currentSpears = 0;
public GameObject rune1;
public GameObject rune2;
public GameObject rune3;
void Start()
{
currentSpears = PlayerPrefs.GetInt("CurrentSpears", 0).ToString();
}
public static void IncreaseSpears()
{
int spears = PlayerPrefs.GetInt("CurrentSpears", 0);
PlayerPrefs.SetInt("CurrentSpears", spears++);
}
public void RuneCollection()
{
if (currentSpears >= 2)
{
Debug.Log("3 runes collected!");
rune3.SetActive(true);
totalRunes = 3;
}
else if (currentSpears >= 1)
{
Debug.Log("2 runes collected!");
rune2.SetActive(true);
totalRunes = 2;
}
else
{
Debug.Log("1 rune collected!");
rune1.SetActive(true);
totalRunes = 1;
}
}
You can save your data with PlayerPrefs for some single values or PersistentDataPath for some multiple values such as a class. Find out more with this link, and you can use them as below.
PlayerPrefs:
private int score = 0;
//set value
PlayerPrefs.SetInt("Score", score);
//get value
private int savedScore = PlayerPrefs.GetInt("Score");
PersistentDataPath:
private string savedName;
private int savedHealth;
private string loadedName;
private int loadedHealth;
public void Save(){
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/FileName.dat",
FileMode.Create);
PlayerClass newData = new PlayerClass();
newData.health = savedHealth;
newData.name = savedName;
bf.Serialize(file, newData);
file.Close();
}
public void Load(){
if (File.Exists(Application.persistentDataPath + "/FileName.dat")){
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/FileName.dat", FileMode.Open);
ObjData newData = (ObjData)bf.Deserialize(file);
file.Close();
loadedHealth = newData.health;
loadedName = newData.name;
}
}
[Serializable]
class PlayerClass{
public string name;
public int health;
}
I have ExoPlayer which plays HLS videos, the thing is i need to give user ability to change video quality(auto/1080/720/480).
I figured out that playing around with AdaptiveTrackSelection.Factory does set the quality, but it remains till the object is killed.
I have also tried using MappingTrackSelector, i know that my video has 4 tracks, but i did not get how to select any of it manually. Will this selection make it work?
Thanks for any ideas.
MappingTrackSelector.MappedTrackInfo trackInfo = mDefaultTrackSelector.getCurrentMappedTrackInfo();
mDefaultTrackSelector.selectTracks(
//what should go here?
, trackInfo.getTrackGroups(4));
Regarding this thread :https://github.com/google/ExoPlayer/issues/2250, I managed to change exo player video quality while playing previous one, so it does not getting in buffering instantly.
So I have next classes :
public enum HLSQuality {
Auto, Quality1080, Quality720, Quality480, NoValue
}
class HLSUtil {
private HLSUtil() {
}
#NonNull
static HLSQuality getQuality(#NonNull Format format) {
switch (format.height) {
case 1080: {
return HLSQuality.Quality1080;
}
case 720: {
return HLSQuality.Quality720;
}
case 480:
case 486: {
return HLSQuality.Quality480;
}
default: {
return HLSQuality.NoValue;
}
}
}
static boolean isQualityPlayable(#NonNull Format format) {
return format.height <= 1080;
}
}
public class ClassAdaptiveTrackSelection extends BaseTrackSelection {
public static final class Factory implements TrackSelection.Factory {
private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate = 2000000;
private final int minDurationForQualityIncreaseMs = 10000;
private final int maxDurationForQualityDecreaseMs = 25000;
private final int minDurationToRetainAfterDiscardMs = 25000;
private final float bandwidthFraction = 0.75f;
private final float bufferedFractionToLiveEdgeForQualityIncrease = 0.75f;
public Factory(BandwidthMeter bandwidthMeter) {
this.bandwidthMeter = bandwidthMeter;
}
#Override
public ClassAdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality reset to Auto");
sHLSQuality = HLSQuality.Auto;
return new ClassAdaptiveTrackSelection(
group,
tracks,
bandwidthMeter,
maxInitialBitrate,
minDurationForQualityIncreaseMs,
maxDurationForQualityDecreaseMs,
minDurationToRetainAfterDiscardMs,
bandwidthFraction,
bufferedFractionToLiveEdgeForQualityIncrease
);
}
}
private static HLSQuality sHLSQuality = HLSQuality.Auto;
private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate;
private final long minDurationForQualityIncreaseUs;
private final long maxDurationForQualityDecreaseUs;
private final long minDurationToRetainAfterDiscardUs;
private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease;
private int selectedIndex;
private int reason;
private ClassAdaptiveTrackSelection(TrackGroup group,
int[] tracks,
BandwidthMeter bandwidthMeter,
int maxInitialBitrate,
long minDurationForQualityIncreaseMs,
long maxDurationForQualityDecreaseMs,
long minDurationToRetainAfterDiscardMs,
float bandwidthFraction,
float bufferedFractionToLiveEdgeForQualityIncrease) {
super(group, tracks);
this.bandwidthMeter = bandwidthMeter;
this.maxInitialBitrate = maxInitialBitrate;
this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L;
this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease = bufferedFractionToLiveEdgeForQualityIncrease;
selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
reason = C.SELECTION_REASON_INITIAL;
}
#Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs, long availableDurationUs) {
long nowMs = SystemClock.elapsedRealtime();
// Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex;
selectedIndex = determineIdealSelectedIndex(nowMs);
if (selectedIndex == currentSelectedIndex) {
return;
}
if (!isBlacklisted(currentSelectedIndex, nowMs)) {
// Revert back to the current selection if conditions are not suitable for switching.
Format currentFormat = getFormat(currentSelectedIndex);
Format selectedFormat = getFormat(selectedIndex);
if (selectedFormat.bitrate > currentFormat.bitrate
&& bufferedDurationUs < minDurationForQualityIncreaseUs(availableDurationUs)) {
// The selected track is a higher quality, but we have insufficient buffer to safely switch
// up. Defer switching up for now.
selectedIndex = currentSelectedIndex;
} else if (selectedFormat.bitrate < currentFormat.bitrate
&& bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
// The selected track is a lower quality, but we have sufficient buffer to defer switching
// down for now.
selectedIndex = currentSelectedIndex;
}
}
// If we adapted, update the trigger.
if (selectedIndex != currentSelectedIndex) {
reason = C.SELECTION_REASON_ADAPTIVE;
}
}
#Override
public int getSelectedIndex() {
return selectedIndex;
}
#Override
public int getSelectionReason() {
return reason;
}
#Override
public Object getSelectionData() {
return null;
}
#Override
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
if (queue.isEmpty()) {
return 0;
}
int queueSize = queue.size();
long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs;
if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) {
return queueSize;
}
int idealSelectedIndex = determineIdealSelectedIndex(SystemClock.elapsedRealtime());
Format idealFormat = getFormat(idealSelectedIndex);
// If the chunks contain video, discard from the first SD chunk beyond
// minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal
// track.
for (int i = 0; i < queueSize; i++) {
MediaChunk chunk = queue.get(i);
Format format = chunk.trackFormat;
long durationBeforeThisChunkUs = chunk.startTimeUs - playbackPositionUs;
if (durationBeforeThisChunkUs >= minDurationToRetainAfterDiscardUs
&& format.bitrate < idealFormat.bitrate
&& format.height != Format.NO_VALUE && format.height < 720
&& format.width != Format.NO_VALUE && format.width < 1280
&& format.height < idealFormat.height) {
return i;
}
}
return queueSize;
}
private int determineIdealSelectedIndex(long nowMs) {
if (sHLSQuality != HLSQuality.Auto) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality seeking for " + String.valueOf(sHLSQuality));
for (int i = 0; i < length; i++) {
Format format = getFormat(i);
if (HLSUtil.getQuality(format) == sHLSQuality) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality set to " + String.valueOf(sHLSQuality));
return i;
}
}
}
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality seeking for auto quality " + String.valueOf(sHLSQuality));
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE
? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction);
int lowestBitrateNonBlacklistedIndex = 0;
for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
Format format = getFormat(i);
if (format.bitrate <= effectiveBitrate && HLSUtil.isQualityPlayable(format)) {
Log.d(ClassAdaptiveTrackSelection.class.getSimpleName(), " Video player quality auto quality found " + String.valueOf(sHLSQuality));
return i;
} else {
lowestBitrateNonBlacklistedIndex = i;
}
}
}
return lowestBitrateNonBlacklistedIndex;
}
private long minDurationForQualityIncreaseUs(long availableDurationUs) {
boolean isAvailableDurationTooShort = availableDurationUs != C.TIME_UNSET
&& availableDurationUs <= minDurationForQualityIncreaseUs;
return isAvailableDurationTooShort
? (long) (availableDurationUs * bufferedFractionToLiveEdgeForQualityIncrease)
: minDurationForQualityIncreaseUs;
}
static void setHLSQuality(HLSQuality HLSQuality) {
sHLSQuality = HLSQuality;
}
}
Hope it helps someone.
You can check out ExoPlayer_TrackSelection from github for changing video quality manually.
Im new to android dev and using andengine and ive just come across a problem when dealing with a large animation that covers more then 1 sprite sheet. Basically I have a large sprite whose animation runs across 2 sprite sheets. Im trying to find a way to load them successfully. I will show you what I am trying and hopefully some one can either show me the correct way or help me finish it my way.
i start off by creating the 2 texture packs from the xml files.
these are created fine
TexturePackTextureRegionLibrary packer1 = null,packer2 = null;
TexturePack spritesheetTexturePack1 = null,spritesheetTexturePack2 = null;
try {
spritesheetTexturePack1 = new TexturePackLoader(activity.getTextureManager(), "Animation/Jack/")
.loadFromAsset(activity.getAssets(), "Jack_walk1" + ".xml");
spritesheetTexturePack1.loadTexture();
packer1 = spritesheetTexturePack1.getTexturePackTextureRegionLibrary();
} catch (final TexturePackParseException e) {
Debug.e(e);
}
TexturePackerTextureRegion textureRegion = packer1.get(Jack_walk1.LOOP_JACK_WALK_TO_SAFE_AREA_00000_ID);
try {
spritesheetTexturePack2 = new TexturePackLoader(activity.getTextureManager(), "Animation/Jack/")
.loadFromAsset(activity.getAssets(), "Jack_walk2" + ".xml");
spritesheetTexturePack2.loadTexture();
packer2 = spritesheetTexturePack2.getTexturePackTextureRegionLibrary();
} catch (final TexturePackParseException e) {
Debug.e(e);
}
TexturePackerTextureRegion textureRegion2 = packer2.get(Jack_walk1.LOOP_JACK_WALK_TO_SAFE_AREA_00000_ID);
ArrayList<SparseArray> animList = new ArrayList<SparseArray>();
animList.add(packer1.getIDMapping());
animList.add(packer2.getIDMapping());
TiledTextureRegion text1 = TiledTextureRegion.create(textureRegion.getTexture(), (int) textureRegion.getTextureX(), (int) textureRegion.getTextureY(), animList);
I then added this function to the tiledtextureregion to take in a list of arrays that hold the frame information and step through adding them to the itexturregion array
public static TiledTextureRegion create(final ITexture pTexture, final int pTextureX, final int pTextureY, final ArrayList<SparseArray> animList) {
ITextureRegion[] textureRegions = null;
int maxFrame = 0;
for(int i = 0; i < animList.size(); i++){
maxFrame += animList.get(i).size();
}
int currentFrame = 0;
textureRegions = new ITextureRegion[maxFrame];
for(int i = 0 ; i < animList.size(); i++){
SparseArray<? extends ITexturePackTextureRegion> packer = animList.get(i);
for(int j = 0; j < packer.size(); j++) {
if (packer.valueAt(j)!= null){
final int x = (int) packer.valueAt(j).getTextureX();
final int y = (int) packer.valueAt(j).getTextureY();
final int width = packer.valueAt(j).getSourceWidth();
final int height = packer.valueAt(j).getSourceHeight();
final Boolean rotated = packer.valueAt(j).isRotated();
textureRegions[currentFrame] = new TextureRegion(pTexture, x, y, width, height, rotated);
currentFrame++;
}
}
}
return new TiledTextureRegion(pTexture, false, textureRegions);
}
but the line return new TiledTextureRegion(pTexture, false, textureRegions); is expecting 1 texture do retrieve the frames from when creating the tiled region. Any ideas where i should go from here or is there a super easy way to handle this that i have over looked. Thanks for any help
This class is designed to work with a single texture.
If you cannot merge your textures into one (which I think is the case), then you can try to write a new class implementing ITextureRegion that will contain 2 or more TiledTextureRegion objects, and which will have a method to select one of these at will.
You will just have to delegate the remaining methods of ITextureRegion to the selected object.
public class MultipleTextureRegion implements ITextureRegion {
private List<TiledTextureRegion> internal;
private int selected=0;
//...
public void add(TiledTextureRegion region) {
internal.add(region);
}
public void select(int index) {
selected=index;
}
//...
// Delegates all ITextureRegion methods ...
public int getWidth() { return internal.get(selected).getWidth(); }
// And so on...
}
How do I get the value of a Theme attribute programmatically?
For example:
Theme:
<style name="Theme">
... truncated ....
<item name="textAppearanceLarge">#android:style/TextAppearance.Large</item>
</style>
Code:
int textSize = ???? // how do I get Theme.textAppearanceLarge?
EDIT: too many answers that don't address the question.
use this function :
myView.setTextAppearance(Context context, int resid)
//Sets the text color, size, style, hint color, and highlight color from the specified TextAppearance resource.
see : http://developer.android.com/reference/android/widget/TextView.html#setTextAppearance%28android.content.Context,%20int%29
From TextView.java this is what the above function does:
public void setTextAppearance(Context context, int resid) {
TypedArray appearance =
context.obtainStyledAttributes(resid,
com.android.internal.R.styleable.TextAppearance);
int color;
ColorStateList colors;
int ts;
.
.
.
ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
TextAppearance_textSize, 0);
if (ts != 0) {
setRawTextSize(ts);
}
int typefaceIndex, styleIndex;
typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
TextAppearance_typeface, -1);
styleIndex = appearance.getInt(com.android.internal.R.styleable.
TextAppearance_textStyle, -1);
setTypefaceByIndex(typefaceIndex, styleIndex);
appearance.recycle();
}
This is another way of doing this. Do make sure you recycle the appearance (TypedArray obect). Hope this helps!
This should do the trick:
int textAppearance = android.R.attr.textAppearanceLarge;
myTextView.setTextAppearance(context, textAppearance);
I ended up with the following code:
public class TextAppearanceConsts
{
private static final String LOG_TAG = "TextAppearanceConsts";
public static final int[] styleable_TextAppearance;
public static final int styleable_TextAppearance_textColor;
public static final int styleable_TextAppearance_textSize;
public static final int styleable_TextAppearance_typeface;
public static final int styleable_TextAppearance_fontFamily;
public static final int styleable_TextAppearance_textStyle;
static {
// F*ing freaking code
int ta[] = null, taTc = 0, taTs = 0, taTf = 0, taFf = 0, taTst = 0;
try{
Class<?> clazz = Class.forName("com.android.internal.R$styleable", false, TextAppearanceConsts.class.getClassLoader());
ta = (int[])clazz.getField("TextAppearance").get(null);
taTc = getField(clazz, "TextAppearance_textColor");
taTs = getField(clazz, "TextAppearance_textSize");
taTf = getField(clazz, "TextAppearance_typeface");
taFf = getField(clazz, "TextAppearance_fontFamily");
taTst = getField(clazz, "TextAppearance_textStyle");
}catch(Exception e){
Log.e(LOG_TAG, "Failed to load styleable_TextAppearance", e);
}
styleable_TextAppearance = ta;
styleable_TextAppearance_textColor = taTc;
styleable_TextAppearance_textSize = taTs;
styleable_TextAppearance_typeface = taTf;
styleable_TextAppearance_fontFamily = taFf;
styleable_TextAppearance_textStyle = taTst;
}
private static int getField(Class<?> clazz, String fieldName)
throws IllegalAccessException
{
try{
return clazz.getField(fieldName).getInt(null);
}catch(NoSuchFieldException nsfe){
Log.e(LOG_TAG, nsfe.toString());
return -1;
}
}
}
Usage example:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RangeBar, 0, 0);
TypedArray appearance = null;
int ap = ta.getResourceId(R.styleable.RangeBar_textAppearance, -1);
if (ap != -1) {
appearance = context.getTheme().obtainStyledAttributes(ap, TextAppearanceConsts.styleable_TextAppearance);
int n = appearance.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = appearance.getIndex(i);
if (attr == TextAppearanceConsts.styleable_TextAppearance_textColor){
mTextColor = appearance.getColorStateList(attr);
} else if (attr == TextAppearanceConsts.styleable_TextAppearance_textSize){
mTextSize = appearance.getDimensionPixelSize(attr, mTextSize);
} else if (attr == TextAppearanceConsts.styleable_TextAppearance_typeface){
mTypefaceIndex = appearance.getInt(attr, -1);
} else if (attr == TextAppearanceConsts.styleable_TextAppearance_fontFamily){
mFontFamily = appearance.getString(attr);
} else if (attr == TextAppearanceConsts.styleable_TextAppearance_textStyle){
mFontStyleIndex = appearance.getInt(attr, -1);
} else {
....
}
}
appearance.recycle();
}
The R.styleable.RangeBar_textAppearance is my attribute, but you can access Android attributes this way:
final static String ANDROID_NS = "http://schemas.android.com/apk/res/android";
final int textAppearanceResId = attrs.getAttributeResourceValue(ANDROID_NS, "textAppearance", 0);
....
int ap = ta.getResourceId(textAppearanceResId, -1);
I know the method is some kind of hacking, but don't know any other better one :(
I've been trying to figure this out for a while now... I need to place marks over top of a seekBar to show the user places that they bookmarked in the past. The data is stored in xml. The problem is making the little ovals appear over the seekBar... It just doesn't work...
Here's my code:
public class seekMark extends View {
private int seekLength; // in pixels
private int seekLeftPad; // in pixels
private int seekBottomPad; // in pixels
private int trackLength; // in ms
private float pxOverMs; // in px/ms
ShapeDrawable lmark;
private seekMark instance;
public seekMark(Context context){
super(context);
instance = this;
seekLength = progressBar.getWidth();
seekLeftPad = progressBar.getPaddingLeft();
seekBottomPad = progressBar.getBottom();
trackLength = player.getDuration();
pxOverMs = pxPerMs();
lmark = new ShapeDrawable(new OvalShape());
}
private float pxPerMs(){
return ((float) seekLength)/((float) trackLength);
}
private int[] markPxList() throws XmlPullParserException, IOException {
int bmStartTime = 0;
String bmNames[] = bmNameList(xmlPath);
int[] bmPos = new int[bmNames.length];
for(int i=0; i < bmNames.length; i++){
bmStartTime = getBookmark(xmlPath, bmNames[i]);
bmPos[i] = (int) (bmStartTime * pxOverMs);
}
return (bmPos);
}
public void markPlace() throws XmlPullParserException, IOException {
int y = seekBottomPad;
int x = 0;
int bmPos[] = markPxList();
for(int i = 0; i < bmPos.length; i++){
x = bmPos[i] + seekLeftPad;
lmark = new ShapeDrawable();
lmark.getPaint().setColor(0xff74AC23);
lmark.setBounds(x, y, x + 1, y + 1);
instance.invalidate();
}
}
protected void onDraw(Canvas canvas) {
lmark.draw(canvas);
}
}
It's called from onCreate using this code. I call it using in another thread to avoid the problem where the dimensions of progressBar aren't yet set in onCreate.
Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if (display.getRotation() == 1){ // if landscape
final Runnable runner = new Runnable() {
public void run() {
seekMark seekMarks = new seekMark(context);
try {
seekMarks.markPlace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// runs in another thread to avoid the problem with calling
// seekMark directly from onCreate
}
};
handler.postDelayed(runner, 1000);
}
The program crashes whenever I try to call seekMark.markPlace()... I'm trying to draw this over top of my layout main.xml.
im not sure if this is what you are trying to do.
Customize Seekbar
this seems to be similar while the approach is different.