I have already looked through some solutions of how to highlight some text in TextView using Spannable class. But it only allows to highlight a snippet which consists of characters. And what if I want to highlight a text row including TextView's width, but a text in this row doesn't fill whole view's width?
If anybody had an experience in such cases I would be glad to take an advice.
Update:
Ok, I hope following images will bring some clarity of my aim.
This is waht I can achieve using Spannable:
And this is what I want:
I really hope that it's clear.
There might be an easier way to do this, but I believe you will need to implement a class that implements LineBackgroundSpan to do what you want. Here's some sample code:
public class MyActivity extends Activity {
private static class MySpan implements LineBackgroundSpan {
private final int color;
public MySpan(int color) {
this.color = color;
}
#Override
public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline,
int bottom, CharSequence text, int start, int end, int lnum) {
final int paintColor = p.getColor();
p.setColor(color);
c.drawRect(new Rect(left, top, right, bottom), p);
p.setColor(paintColor);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final TextView tv = new TextView(this);
setContentView(tv);
tv.setText("Lines:\n", BufferType.EDITABLE);
appendLine(tv.getEditableText(), "123456 123 12345678\n", Color.BLACK);
appendLine(tv.getEditableText(), "123456 123 12345678\n", Color.RED);
appendLine(tv.getEditableText(), "123456 123 12345678\n", Color.BLACK);
}
private void appendLine(Editable text, String string, int color) {
final int start = text.length();
text.append(string);
final int end = text.length();
text.setSpan(new MySpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
you can do android:padding="5dp" in your xml layout file and you can highlight noramlly through style xml and Spannable String Hope it will work welcome
TextView TV = (TextView)findViewById(R.id.text);
TV.setText("Italic, highlighted, bold.", TextView.BufferType.SPANNABLE);
Spannable WordtoSpan = (Spannable) TV.getText();
WordtoSpan.setSpan(new BackgroundColorSpan(0xFFFFFF00), 8, 19, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
TV.setText(WordtoSpan);
Link1
This may help you
Save below code in drawable folder
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="<<your color code"/>
</shape>
and specify textview background as below
android:background="#drawable/<<your file name>>"
What you are trying to do can be done more easily using StateListDrawable.
Make all your TextView's focusable in touch mode. Define statelistdrawables for the textview's background color and text color, and call requestFocus() whenever you want to highlight a textview.
StateListDrawable for textview's background :
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_focused="true"
android:drawable="#0F0" />
<item android:drawable="#0000" />
</selector>
Similarly define a selector list for text color.
Related
I got stuck in this problem since yesterday. I am using spannable string and want to add custom shape to it similar to chips:
Here is my code where I am modifying the spannable string:
s.setSpan(new BackgroundColorSpan(backgroundColor), currentIndex, currentIndex + words[i].length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
I tried with Bitmap but according to process I need to convert my Editable with BitmapDrawable as per the code:
BitmapDrawable bd = (BitmapDrawable) convertViewToDrawable(textView);
The problem I am facing is that I am not using textView. I am working on AutoCompleteTextView and converting AutoCompleteTextView to Bitmap is impossible (If I am not wrong here).
How can I achieve the similar result?
Edit-1
I used following shape:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="1dp" android:color="#A6B0B8" />
<gradient android:startColor="#E5E5E6"
android:endColor="#B4B6B7"
android:angle="-270"/>
<corners android:bottomRightRadius="10dp"
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp"></corners>
</shape>
Custom Drawable
public class CustomDrawble extends DynamicDrawableSpan {
private Context mContext;
CustomDrawble(Context context){
super();
this.mContext = context;
}
#Override
public Drawable getDrawable() {
Resources res = mContext.getResources();
Drawable drawable = res.getDrawable(R.drawable.bubble);
drawable.setBounds(0,0,100,20);
return drawable;
}
}
For applying above Drawable I used following code:
s.setSpan(new CustomDrawble(mContext), currentIndex, currentIndex + words[i].length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
From the #Edit-1 section I am getting this:
What I am doing wrong here? I am using Widget.SearchView not a TextView
Currently, I'm trying to make an infinitely long chain of ImageViews that are all next to each other.
I attempt to do this by using addRule and LayoutParam so that every new ImageView has to be set to the right of the ImageView before it.
However, I encounter an odd problem. When I do run my code, this is the result :
Not only are my two ImageViews not even next to each other, it doesn't work for any number past two! If I just create two blocks, it gives the result below.
If I make any more blocks than 2, they'll just stack on top of block #2. Which is what happens in the image above.
Also, I have to set every ImageView with its own ID because if I don't, they'll all have an ID of -1.
I've been stuck on this for like 5 hours in a row, and it's really starting to get annoying. (Please do not suggest that I use a LinearLayout. I already know, and I can't use it.)
public class TerrainFactory {
int count = 0;
int idCount = 1;
Terrain terrain;
public Terrain createNewTerrain(Activity activity, RelativeLayout relativeLayout,
final ArrayList<Terrain> terrainArrayList){
//TODO: Add the rectangle bounds into this.
terrain = new Terrain();
terrain.is = activity.getResources().openRawResource(+R.drawable.game_tile);
terrain.tile = BitmapFactory.decodeStream(terrain.is);
terrain.tileDrawable = new BitmapDrawable(activity.getResources(), terrain.tile);
terrain.terrainImage = new ImageView(activity);
terrain.terrainImage.setImageDrawable(terrain.tileDrawable);
//noinspection ResourceType
terrain.terrainImage.setId(idCount);
idCount++;
if(count >= 1) {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);;
layoutParams.addRule(RelativeLayout.RIGHT_OF, terrainArrayList.get(count - 1).terrainImage.getId());
relativeLayout.addView(terrain.terrainImage, layoutParams);
terrain.terrainImage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//TODO: Set rectangle bounds in here
++count;
terrain.terrainImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}else{
++count;
relativeLayout.addView(terrain.terrainImage);
terrain.terrainImage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//TODO: Set rectangle bounds in here
terrain.terrainImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
return terrain;
}
public int getCount() { return count; }
}
Here is my PlayActivity class, where all the block creating happens :
public class PlayActivity extends Activity {
RelativeLayout relativeLayout;
TerrainFactory terrainFactory;
ArrayList<Terrain> terrainArrayList = new ArrayList<Terrain>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
relativeLayout = (RelativeLayout) findViewById(R.id.relativeLayout);
terrainFactory = new TerrainFactory();
for(int i = 0; i < 7; i++){
terrainArrayList.add(terrainFactory.createNewTerrain(PlayActivity.this, relativeLayout, terrainArrayList));
}
}
}
And here is the Terrain class
public class Terrain {
public Bitmap tile;
public InputStream is;
public ObjectAnimator moveLeft;
public Drawable tileDrawable;
public Rect bounds;
public ImageView terrainImage;
}
Here's the XML for the RelativeLayout. The RelativeLayout I'm using is the one nested inside the RelativeLayout that fills the entire screen.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hooray.project.fun.activity.PlayActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="false"
android:id="#+id/relativeLayout"></RelativeLayout>
</RelativeLayout>
Basically I jsut would tackle this in another way....using not Views for buildiung terrains but using surfaceview and draw bitmaps on it...
But well lets do it your way:
Place your views into a FrameLayout. Then when you create your views give them their dimension in code and also set the x and y positions in code.
For example this is how I give a View the needed Dimension and the position in my Code. Its a Textview and I jsut set the x and y position which I have stuffed iinto variables so I can chnage them how i want:
tView.setLayoutParams(new FrameLayout.LayoutParams(hintListWidth,hintListHeight));
tView.setX(hintListPosX);
tView.setY(hintListPosY);
maybe for your example do it like this:
float startPosX=???;
float startPosY=???;
float i=0;
for(Terrian t:terrainArrayList)
{
tView.setLayoutParams(new FrameLayout.LayoutParams(width,height));
tView.setX(startPosX+width*i);
tView.setY(startPosY);
i++;
}
If you have the width and height ready and know where to start placing the first view every view will be placed side by side from left to right...
As suggested in the other answer(s) to your question, the problem is that counter on the if-true branch is never incremented before views are drawn (aka tree observer callback being called). You are calling addView but I think this goes on and adds the view asynchronously, while you go on and add views. So you finish adding the views and only after that they are drawn. I suggest taking the counter out of the callback and place it at the end of the if-true block.
Note: if you are targeting SDK17+ you can use View.generateViewId() instead of manually keeping track / incrementing it yourself. Not that it matters in this particular scenario, but it will not allow the generated id to conflict with aapt-generated ids and it will roll it over once the top limit is reached.
I am a real newbie when it comes to android programming. I am trying to figure out how to change view on the fly or draw circles from the main activity.
I have tried to change view but i failed. So now i am trying to figure out how i should paint a circle from my main activity after the client clicks on a button
private void InitiateGame(String name, String password){
Log.d("InitiatingGame", "Initiating Game");
NetworkHandler networkHandler = new NetworkHandler(HOST, PORT);
PlayerHandler playerHandler = new PlayerHandler();
MessageHandler messageHandler = new MessageHandler(networkHandler, playerHandler);
networkHandler.connect(name, password);
final GameHandler zombieView = new GameHandler(networkHandler, messageHandler, playerHandler);
nameField.post(new Runnable()
{
#Override
public void run()
{
setMainScreenVisibility(View.INVISIBLE);
}
});
initiated = true;
}
This is the code that is called after the client have clicked on the "Connect" Button. So he will instanciate some classes that you guys dont need to know what they are doing. Then he connects to the server. So i do not want to make new intent's.
What is better. Create a new view class that extends view that i set as contentview?(if so. how?) Or should i just try to draw these circles from this main activity? (also how?)
To draw a circle using paint , you need to create a custom view like this
private class CircleView extends View{
Paint paint = new Paint();
public CircleView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
paint.setColor(Color.GREEN);
// set your own position and radius
canvas.drawCircle(100,200,100,paint);
}
}
And, then add an Parent Layout to the Activity
RelativeLayout relativeLayout = new RelativeLayout(this);
relativeLayout.setLayoutParams(new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(relativeLayout);
Finally you need to add the circle view to the Parent Layout
relativeLayout.addView(new CircleView(this));
Another way is to create the circle as drawable xml and set it to an ImageView
Create a circle.xml in drawable/
And then , set the drawable in ImageView in your layout
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/circle"/>
You could just apply a circle.xml as a drawable to a view.
<!-- circle.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="#color/my_fancy_color"/>
</shape>
<!-- a view in one of your layout files -->
<View
android:width="120dp"
android:height="120dp"
android:background="#drawable/circle"
/>
The circle.xml can be named anything and show be place in the drawables folder. If you change the name of the xml then reference it with the changed name.
I want to create a Button in Android with a text, and a background image. The background image should crossfade every X time.
I have this working using a TransitionDrawable with 2 images.
But I can't get this to work with more than 2 images.
What I have :
In Java code I create a Button and set a background (which is a TransitionDrawable defined in XML). And I start the transition.
final Button b = new Button(getApplicationContext());
b.setTextColor(getResources().getColor(R.color.white));
b.setText("Some text");
b.setBackgroundDrawable(getResources().getDrawable(R.drawable.tile));
StateListDrawable background = (StateListDrawable) b.getBackground();
TransitionDrawable td = (TransitionDrawable) background.getCurrent();
td.startTransition(2000);
In XML I define in tile.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<solid
android:color="#449def" />
</shape>
</item>
<item android:drawable="#drawable/transition">
<shape>
<solid
android:color="#0000ff" />
</shape>
</item>
</selector>
And finally a transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="#drawable/desert"/>
<item android:drawable="#drawable/hydrangeas" />
<item android:drawable="#drawable/jellyfish" />
</transition>
Now the effect is that when I start the app the desert image is shown. This image crossfades to the hydrangeas image as it should. But the jellyfish image is never shown.
In the doc for TransitionDrawables it is stated that you can specify more than 2 drawables but I can't get this to work.
I also tried this without any XML but in pure JAVA but this gave exactly the same problem :-(
You can do it by using a handler
mAnimateImage is your button
int DrawableImage[] = {R.drawable.back_red, R.drawable.back_green, R.drawable.back_purple};
final Handler handler = new Handler();
final int[] i = {0};
final int[] j = {1};
handler.postDelayed(new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
Resources res = getApplicationContext().getResources();
TransitionDrawable out = new TransitionDrawable(new Drawable[]{res.getDrawable(DrawableImage[i[0]]), res.getDrawable(DrawableImage[j[0]])});
out.setCrossFadeEnabled(true);
mAnimateImage.setBackgroundDrawable(out);
out.startTransition(4000);
i[0]++;
j[0]++;
if (j[0] == DrawableImage.length) {
j[0] = 0;
}
if (i[0] == DrawableImage.length) {
i[0] = 0;
}
handler.postDelayed(this, 8000);
}
});
}
}, 0);
According to the official documentation, TransitionDrawable can only cross-fade among 2 layers, quoting from the official android reference.
An extension of LayerDrawables that is intended to cross-fade between
the first and second layer. To start the transition, call
startTransition(int). To display just the first layer, call
resetTransition().
If you don't read it carefully, since it extends LayerDrawables, which can have multiple layers, one may expect that you could cross-fade from N layers. But it is very clear, startTransition shows the second layer, resetTransition shows the first.
I suggest you do your own implementation for multiple transitions. What I'd do is to have 2 images and animate them. You may need to set the drawables by hand, but it should be a quite simple piece of code.
in appendix operating time you can dynamically change pictures
Use td.setDrawableByLayerId(td.getId(1), drawable) on your TransitionDrawable
TransitionDrawable transitionDrawable = (TransitionDrawable) myImage
.getDrawable();
transitionDrawable.setDrawableByLayerId(transitionDrawable.getId(1), getResources()
.getDrawable(R.drawable.c));
You can only transition maximum two images with TransitionDrawable. To work with more than two images you can extend LayerDrawable and implement your own TransitionDrawable.
Here's the ready implementation of custom TransitionDrawable to work with more than two images.
You can see the complete sample along with the gif demo here on Github.
No need to use handler, it's more efficient + cleaner to use coroutines instead of a handler and as a bonus you can make this transition between as many drawables as you like:
/**
* Run multitransition animation on a view.
*
* #param drawableIds The drawables to load into the view, one after the other
* #param delayBetweenTransitions the number of milliseconds to transition from one drawable to the next
* #param viewToAnimate The view to animate.
*/
fun runMultitransitionAnimation(#DrawableRes vararg drawableIds: Int,
delayBetweenTransitions : Int,
viewToAnimate : View) {
CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main.immediate) {
val delay = SOME_INTEGER_MILLISECONDS
for (i in 0 until drawableIds.size-1) {
val nextTransition = TransitionDrawable(arrayOf(
context.getDrawable(drawableIds[i]),
context.getDrawable(drawableIds[i+1])))
))
nextTransition.isCrossFadeEnabled = true
viewToAnimate.background = nextTransition
nextTransition.startTransition(delayBetweenTransitions)
delay(delayBetweenTransitions*2)
}
}
}
You'll need to just make a new TransitionDrawable per iteration.
This will step up through your index of all the images you add, you can have any number of images...
private Handler handler = new Handler();
private void startImageCrossfading(#IdRes int imageViewResId, #DrawableRes int... drawableIds) {
Drawable[] bitmapDrawables = new Drawable[drawableIds.length];
for (int i = 0; i < drawableIds.length; i++) {
// if you are using Vectors cross fade won't work unless you do this! - See my answer at https://stackoverflow.com/a/54583929/114549
// bitmapDrawables[i] = getBitmapDrawableFromVectorDrawable(this, drawableIds[i]);
bitmapDrawables[i] = ContextCompat.getDrawable(this, drawableIds[i]);
}
final ImageView imageView = findViewById(imageViewResId);
handler.postDelayed(new Runnable() {
int index = 0;
#Override
public void run() {
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
bitmapDrawables[index % bitmapDrawables.length],
bitmapDrawables[++index % bitmapDrawables.length]
});
td.setCrossFadeEnabled(true);
imageView.setImageDrawable(td);
td.startTransition(500); // transitionDurationMillis
handler.postDelayed(this, 3000);
}
}, 3000);
}
On pause you should clean up the handler
handler.removeCallbacksAndMessages(null);
I am trying to create a seekbar with a thumb that changes color when it is pressed by the user and change back when the user lets go. I managed to change the thumb drawable using set thumb. However, I also had to set the drawable boundries in java to get it to appear after the change. Now when the user lets go of the slider and it reverts to its original drawable it resets the drawable to the zero position and leaves the progress where it should be. I have tried using a global variable to reset the seekbar progress after the drawable is changed, but it doesn't seems to work. If I use a static integer to reset the progress it works the way I want it to.
Kinda new to android and I don't know what I am doing wrong.
This is the code that changes the drawable when it is touched.
private void Seek_Height_Click() {
Drawable myThumb = getResources().getDrawable(R.drawable.slider_button_click);
myThumb.setBounds(new Rect(0, 0, myThumb.getIntrinsicWidth(),myThumb.getIntrinsicHeight()));
Height_of_Target_skbr.setThumb(myThumb);
Height_of_Target_skbr.setThumbOffset(-1);
}
This is the code to set the thumb back to the original drawable.
private void Seek_Height_UnClick() {
Drawable myThumb = getResources().getDrawable(R.drawable.slider_button);
myThumb.setBounds(new Rect(0, 0, myThumb.getIntrinsicWidth(),myThumb.getIntrinsicHeight()));
Height_of_Target_skbr.setThumb(myThumb);
Height_of_Target_skbr.setThumbOffset(-1);
Height_of_Target_skbr.setProgress(Height_Prog_int);
}
This is the code that calls the above method.
this.Height_of_Target_skbr.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
returnHeight_skbr();
Seek_Height_UnClick();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
returnHeight_skbr();
Seek_Height_Click();
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
returnHeight_skbr();
}
});
I was able to make this work using by creating a style in the drawable folder and then setting the thumb drawable to that style.
I refereed to this tutorial to make this work.
http://www.youtube.com/watch?v=zXXCFmfJMNw
Code for Thumb Style
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:state_pressed="true"
android:drawable="#drawable/slider_button_click">
</item>
<item
android:drawable="#drawable/slider_button">
</item>
</selector>