How to center align text in JTextArea? - swing

I have requirement of Text wrapping + Text Centering + Nimbus Look and Feel + with a background color black.
I tried out with the following 2 components
1. JTextPane
-------supports: Text Wrapping + Text Centering using StyledDocument
-------issue : custom background color doesn't work Nimbus Look and Feel(be it disabled or enabled)
which is bug as show here http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=ab1938d61a7fd83ca2b54eb8df7?bug_id=6789980
my jdk version is : 1.6.0_33
Fixes tried :
UIManager.getLookAndFeelDefaults().put("TextPane.background", Color.RED);
but this also is unable to override the Nimbus default which is #d6d9df (214,217,223)
Can anyone help me with text Centering in the JTextArea?
Thanks in advance
1. JTextArea
-------supports: Text Wrapping + background coloring with Nimbus LnF (only when its enabled)
-------issue : Text Centering is an issue in here

To center text you should use a JTextPane not a JTextArea. Centering text is a big issue.
It is better to solve the background problem with the JTextPane.
When using the Nimbus LAF it looks like you need to provide a custom Painter to just paint the background as a solid color. Check out this answer by #mKorbel. You would need to change the property tag. Also in the FillPainter I changed:
g.setColor(color);
g.setColor(object.getBackground());

Thanks .... Working ,,,,
This is what I used ...
UIManager.getLookAndFeelDefaults().put("TextPane[Enabled].backgroundPainter", new FillPainter());`
public class FillPainter implements Painter<JComponent> {
#Override
public void paint(Graphics2D g, JComponent object, int width,int height) {
g.setColor(object.getBackground());
}
}

I use this to center text in JTextArea
public static void centerText (JTextArea ta)
{
BufferedImage fake1 = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D fake2 = fake1.createGraphics();
FontMetrics fm = fake2.getFontMetrics(ta.getFont());
int lines = ta.getLineCount();
ArrayList<String> list = new ArrayList<>();
try
{
for (int i = 0; i < lines; i++)
{
int start = ta.getLineStartOffset(i);
int end = ta.getLineEndOffset(i);
String line = ta.getText(start, end - start).replace("\n","");
list.add (line.trim());
}
}
catch (BadLocationException e)
{
System.out.println(e);
}
alignLines (list, fm, ta);
}
private static void alignLines (ArrayList<String> list, FontMetrics fm, JTextArea ta)
{
String leading = " ";
int longest = -1;
for (String s : list)
{
if (fm.stringWidth(s) > longest)
longest = fm.stringWidth(s);
}
for (int n=0; n<list.size(); n++)
{
String s = list.get(n);
if (fm.stringWidth(s) >= longest)
continue;
while (fm.stringWidth(s) < longest)
s = ' '+s+' ';
list.set(n, s);
}
StringBuilder sb = new StringBuilder();
for (String s : list)
{
sb.append(leading).append(s).append('\n');
}
ta.setText (sb.toString());
}
call CenterText with your filled JTextArea as argument

Related

WinRT TextBox MaxLength does not count \n\r as two characters

I have TextBox with MaxLength set to 10 but it is accepting 11 characters when Enter key is pressed. Looks like it is counting \n\r as 1 character instead of two. Is there anyway to make it count \n\r as two char length?
If you really want to allow line breaks in your text box and limit its text length, I see two options:
Either bind MaxLength through a converter so that it changes its value according to how many line breaks (\r\n) the text contains, as shown in this question
Alternatively, you might define your own attached property MaxLength that calculates text length correctly. This might look somewhat like the following (just as an example you'll need to adapt that to take into account special cases etc.):
public class TextBoxExtensions: DependencyObject
{
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.RegisterAttached(
"MaxLength", typeof (int), typeof (MaxLengthBehavior), new PropertyMetadata(default(int), PropertyChangedCallback));
public static void SetMaxLength(DependencyObject element, int value)
{
element.SetValue(MaxLengthProperty, value);
}
public static int GetMaxLength(DependencyObject element)
{
return (int) element.GetValue(MaxLengthProperty);
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var tb = dependencyObject as TextBox;
if (tb != null)
{
tb.KeyDown -= TbOnKeyDown;
tb.KeyDown += TbOnKeyDown;
}
}
private static void TbOnKeyDown(object sender, KeyRoutedEventArgs args)
{
var tb = sender as TextBox;
if (tb != null)
{
int max = GetMaxLength(tb);
if (tb.Text.Length >= max)
args.Handled = true;
}
}
}
<TextBox local:TextBoxExtensions.MaxLength="10" />

Images in JTable cells off by one pixel?

So, I'm able to load images into my JTable's cells now, but for some reason the graphics are all shifted to the right by one pixel, allowing me to see the JTable's background. Any ideas? Sorry if my formatting's off; still not entirely used to this markup.
public static void main(String[] args) {
final int rows = 16;
final int columns = 16;
final int dimTile = 32;
JFrame frame = new JFrame("test");
JTable table = new JTable(rows, columns);
table.setIntercellSpacing(new Dimension(0, 0));
table.setShowGrid(false);
table.setBackground(Color.cyan);
table.setTableHeader(null);
table.setRowHeight(dimTile);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setPreferredSize(new Dimension(rows * dimTile, columns * dimTile));
Tile tile = new Tile(0);
for(int i = 0; i < rows; i++) {
for(int j = 0; j < columns; j++) {
table.getColumnModel().getColumn(j).setCellRenderer(new MyRenderer());
table.setValueAy(tile, i, j);
}
}
JScrollPane scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
frame.getContentPane().add(scrollPane);
frame.setSize(512, 512);
frame.setVisible(true);
int adjustedSizeX = frame.getInsets().left + frame.getInsets().right + 512;
int adjustedSizeY = frame.getInsets().top + frame.getInsets().bottom + 512;
frame.setSize(adjustedSizeX, adjustedSizeY);
frame.pack();
...
}
public class MyRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
Tile tile = (Tile) value;
setIcon(tile.getIcon());
return this;
}
}
public class Tile {
ImageIcon icon;
public Tile(int graphic) {
icon = new ImageIcon(PATH/TO/"...test.png");
}
public ImageIcon getIcon() {
return icon;
}
}
Not entirely sure what you mean by "one pixel off" - but achieve zero intercell spacing without any visual artefacts you have to both zero the margins and turn off the grid lines:
table.setIntercellSpacing(new Dimension(0, 0));
table.setShowGrid(false)
Edit
Okay, looking closer, there are several issues with your code
you do the column sizing indirectly instead of directly (and yet another nice example why to never-ever do a component.setPreferredSize :-)
the renderer's border takes some size
to fix the first, configure each column's width, table layout will automagically configure its itself
final int rows = 16;
final int columns = 16;
Tile tile = new Tile(0);
int tileHeight = tile.getIcon().getIconHeight();
int tileWidth = tile.getIcon().getIconWidth();
JTable table = new JTable(rows, columns);
// remove all margin
table.setIntercellSpacing(new Dimension(0, 0));
table.setShowGrid(false);
table.setTableHeader(null);
// set the rowHeight
table.setRowHeight(tileHeight);
// turn off auto-resize
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
// configure each column with renderer and prefWidth
for (int j = 0; j < columns; j++) {
TableColumn column = table.getColumnModel().getColumn(j);
column.setCellRenderer(new MyRenderer());
column.setPreferredWidth(tileWidth);
}
for the second, null the border in each call:
public static class MyRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
setBorder(null);
Tile tile = (Tile) value;
setIcon(tile.getIcon());
return this;
}
}

Intermediate image

I have a problem with intermediate image. The image is showing only once. After i move the image "line" is not showing anymore.
public void paintLine(Graphics g) {
if (line == null) {
line = new BufferedImage(1, height, BufferedImage.TYPE_INT_ARGB);
Graphics gImg = line.getGraphics();
float[] data = datas[index];
for (float f : data) {
float[] rgb = ColorMap.getPixelColor(f);
gImg.setColor(new Color(rgb[0], rgb[1], rgb[2]));
gImg.drawRect(0, (int)yPos--, 1, 1);
}
gImg.dispose();
}
xIncr++;
g.drawImage(line, (int)xPos - xIncr, (int)yPos, null);
graph.repaint();
}
This method is called in paintComponent ofJPanel.
If i recreate the image "line" each time, it is displaying properly with really poor performance.
Emm... I am not pretty sure owing to less information in your question but, as a rule, image may disappear because of doubleBuffered(false) option. So you need to set the double buffered option to true manually for your canvas. Code something like this
public class MyLabel extends JLabel{
public MyLabel()
{
this.setDoubleBuffered(true);
}
public void paintComponent(Graphics g)
{
this.paintLine(g);
}
public void paintLine(Graphics g) {
if (line == null) {
line = new BufferedImage(1, height, BufferedImage.TYPE_INT_ARGB);
Graphics gImg = line.getGraphics();
float[] data = datas[index];
for (float f : data) {
float[] rgb = ColorMap.getPixelColor(f);
gImg.setColor(new Color(rgb[0], rgb[1], rgb[2]));
gImg.drawRect(0, (int)yPos--, 1, 1);
}
//gImg.dispose();
}
xIncr++;
g.drawImage(line, (int)xPos - xIncr, (int)yPos, null);
graph.repaint();
}
}
Now I guess the conception is more clear
Good luck

JTable row color change based on a column value- on pop up click

My jTable is loaded with data and this is where I call my Pop up functionality on jTable.
jTable.addMouseListener(new TablePopupListener(jTable));
displayTable();
So basically, if I right-click a row, a popup(credit check) comes up and if I click it is setting a value to the last cell in that row. Now, based on this column cell value I have to define the color of a row. Let's say if the cell value fails then turn the row to red else to green. I have tried customCellRenderer and defined my condition but there is no change in row color. The custom cell renderer worked great for a button functionality that I had to write, though. The below code uses prepare cellRenderer which I felt is easy but I don't see any change in row color.
I am missing some connection, plz provide me help.
Thanks in advance.
class TablePopupListener extends MouseAdapter implements ActionListener {
JPopupMenu popup;
JTable table;
int[] selRows;
TableModel model;
ArrayList rowValueList = new ArrayList();
JMenuItem creditCheck = new JMenuItem("Credit Check");
public TablePopupListener(JTable jTable) {
this.table = jTable;
model = table.getModel();
popup = new JPopupMenu();
JMenuItem creditCheck = new JMenuItem("Credit Check");
creditCheck.addActionListener(this);
popup.add(creditCheck);
}
public void mousePressed(MouseEvent me) {
firePopup(me);
}
public void mouseReleased(MouseEvent me) {
firePopup(me);
}
public void firePopup(MouseEvent me) {
/*
* The popup menu will be shown only if there is a row selection in the
* table
*/
// popup.show(table, me.getX(), me.getY());
if (me.isPopupTrigger() && table.getModel().getRowCount() != 0
&& table.getSelectedRow() != -1) {
// popup.show(table,me.getX(),me.getY());
if (me.isPopupTrigger()) {
JTable source = (JTable) me.getSource();
int row = source.rowAtPoint(me.getPoint());
int column = source.columnAtPoint(me.getPoint());
if (!source.isRowSelected(row))
source.changeSelection(row, column, false, false);
popup.show(table, me.getX(), me.getY());
}
}
}
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Credit Check")) {
System.out.println("you have clicked creditCheckpopup");
selRows = table.getSelectedRows();
if (selRows.length > 0) {
for (int i = 0; i < selRows.length; i++) {
// get Table data
for (int j = 1; j < (table.getColumnCount()) - 1; j++) {
rowValueList.add(model.getValueAt(selRows[i], j));
}
System.out.println("Selection : " + rowValueList);
}
} else {
System.out.println("you have clicked something idiot");
}
int result = new COpxDeal(rowValueList).CheckCredit();
if (result == 1)
rowValueList.add("pass");
else
rowValueList.add("fail");
String aValue = (String) rowValueList.get(14);
for (int i = 0; i < selRows.length; i++) {
model.setValueAt(aValue, selRows[i], 15);
}
// inserted comment (Kleopatra): where are we? that's outside of the TablePopup?
// okay, nothing like copying the code into an IDE and let that do the formatting, silly me ;-)
// this is indeed _inside_ the popup, that is the table is recreated
table = new JTable(model) {
public Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent) c;
// if (!isRowSelected(row)){
// c.setBackground(getBackground());
// System.out.println(isRowSelected(row));
// }
int modelRow = convertRowIndexToModel(row);
String strTestValue = "fail";
String strTblValue = (String) getModel().getValueAt(
modelRow, 15);
System.out.println("result :" + strTblValue);
if (strTblValue == null || strTblValue.equals(""))
System.out.println("there is nothing in strTblValue");
else if (strTestValue.equals(strTblValue)) {
jc.setBackground(Color.RED);
} else {
jc.setBackground(Color.green);
}
return c;
}
};
}
}
}
after some formatting (believe me, it's important for code to be readable ;-) seems like you instantiate a new table inside your popupMenu and only that table has the custom renderer. Which you can do, but doesn't have any effect on the your real table.
Move the prepareRenderer into your real table (the one you pass into the popup as parameter) and you should see the coloring. Beware: due to a bug in DefaultTableCellRenderer, you have to set the color always, that is
if (nothingToDo) {
setBackground(normal)
} else if ... {
setBackground(one)
} else {
setBackground(other)
}
Edit: trying to explain the changes in code structure, pseudo-code snippets
Current state, that's what you are doing:
JTable table = new JTable();
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
....
// in the listener guts, the reference is replaced
table = new JTable() {
#Override
Component prepareRenderer(...
}
Change to, that's what you should do:
JTable table = new JTable() {
#Override
Component prepareRenderer(...
};
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
// don't replace ever, it's for reading only
edit 2:
- changed the pseudo-code to actually register the listener)
- the code indented below the addMouseListener is mean as an outline of the code inside the TablePopupListener

Android: How to use the Html.TagHandler?

I am trying to build an android application for a message board. To display formatted html for the post contents I have chosen the TextView and the Html.fromHtml() method. That, unfortunately, covers only a few html tags. The unknown tags are handled by a class that implements TagHandler and has to be generated by myself.
Now, I googled a lot and can't find an example of how this class should work. Let's consider I have an u tag for underlining some text (I know that this is deprecated, but whatever). How does my TagHandler look like?
It is called in the following way:
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
The first two arguments are fine. I guess I have to modify output using output.append(). But how do I attach something underlined there?
So, i finally figured it out by myself.
public class MyHtmlTagHandler implements TagHandler {
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
processStrike(opening, output);
}
}
private void processStrike(boolean opening, Editable output) {
int len = output.length();
if(opening) {
output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, StrikethroughSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for(int i = objs.length;i>0;i--) {
if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
return objs[i-1];
}
}
return null;
}
}
}
And for your TextView you can call this like:
myTextView.setText (Html.fromHtml(text.toString(), null, new MyHtmlTagHandler()));
if anybody needs it.
Cheers
This solution is found in the Android sdk
In android.text.html. Lines 596 - 626. Copy/pasted
private static <T> Object getLast(Spanned text, Class<T> kind) {
/*
* This knows that the last returned object from getSpans()
* will be the most recently added.
*/
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
return objs[objs.length - 1];
}
}
private static void start(SpannableStringBuilder text, Object mark) {
int len = text.length();
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
}
private static <T> void end(SpannableStringBuilder text, Class<T> kind,
Object repl) {
int len = text.length();
Object obj = getLast(text, kind);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
To use, override TagHandler like so:
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
if(opening){
start((SpannableStringBuilder) output, new Strike();
} else {
end((SpannableStringBuilder) output, Strike.class, new StrikethroughSpan());
}
}
}
/*
* Notice this class. It doesn't really do anything when it spans over the text.
* The reason is we just need to distinguish what needs to be spanned, then on our closing
* tag, we will apply the spannable. For each of your different spannables you implement, just
* create a class here.
*/
private static class Strike{}
I took janoliver's answer and came up with my version that attempts to support more options
String text = ""; // HTML text to convert
// Preprocessing phase to set up for HTML.fromHtml(...)
text = text.replaceAll("<span style=\"(?:color: (#[a-fA-F\\d]{6})?; )?(?:font-family: (.*?); )?(?:font-size: (.*?);)? ?\">(.*?)</span>",
"<font color=\"$1\" face=\"$2\" size=\"$3\">$4</font>");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )face=\"'(.*?)', .*?\"", "face=\"$1\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-small\"", "$1size=\"1\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-small\"", "$1size=\"2\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"small\"", "$1size=\"3\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"medium\"", "$1size=\"4\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"large\"", "$1size=\"5\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-large\"", "$1size=\"6\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-large\"", "$1size=\"7\"");
text = text.replaceAll("<strong>(.*?)</strong>", "<_em>$1</_em>"); // we use strong for bold-face
text = text.replaceAll("<em>(.*?)</em>", "<strong>$1</strong>"); // and em for italics
text = text.replaceAll("<_em>(.*?)</_em>", "<em>$1</em>"); // but Android uses em for bold-face
text = text.replaceAll("<span style=\"background-color: #([a-fA-F0-9]{6}).*?>(.*?)</span>", "<_$1>$2</_$1>");
text_view.setText(Html.fromHtml(text, null, new Html.TagHandler() {
private List<Object> _format_stack = new LinkedList<Object>();
#Override
public void handleTag(boolean open_tag, String tag, Editable output, XMLReader _) {
if (tag.startsWith("ul"))
processBullet(open_tag, output);
else if (tag.matches(".[a-fA-F0-9]{6}"))
processBackgroundColor(open_tag, output, tag.substring(1));
}
private void processBullet(boolean open_tag, Editable output) {
final int length = output.length();
if (open_tag) {
final Object format = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH);
_format_stack.add(format);
output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
} else {
applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void processBackgroundColor(boolean open_tag, Editable output, String color) {
final int length = output.length();
if (open_tag) {
final Object format = new BackgroundColorSpan(Color.parseColor('#' + color));
_format_stack.add(format);
output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
} else {
applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private Object getLast(Editable text, Class kind) {
#SuppressWarnings("unchecked")
final Object[] spans = text.getSpans(0, text.length(), kind);
if (spans.length != 0)
for (int i = spans.length; i > 0; i--)
if (text.getSpanFlags(spans[i-1]) == Spannable.SPAN_MARK_MARK)
return spans[i-1];
return null;
}
private void applySpan(Editable output, int length, int flags) {
if (_format_stack.isEmpty()) return;
final Object format = _format_stack.remove(0);
final Object span = getLast(output, format.getClass());
final int where = output.getSpanStart(span);
output.removeSpan(span);
if (where != length)
output.setSpan(format, where, length, flags);
}
}));
This does seem to get the bullets, foreground color, and background color. It might work for the font-face but you might need to supply the fonts as it doesn't seem that Android supports fonts other than Droid/Roboto.
This is more of a proof-of-concept, you might probably want to convert the regex into String processing, since regex doesn't support combining the preprocessing in any way, meaning this takes a lot of passes over the String. This also doesn't seem to get the font size to change, I've tried defining it like "16sp", "medium", or "4" without seeing changes. If anyone has gotten sizes to work, mind sharing?
I currently would like to be able to add numbered/ordered list support to this, i.e.
item
item
item
NOTE:
To people starting with any of this, it seems that the "tag" that is given to handleTag(...) is just the name of the tag (like "span"), and doesn't contain any of the attributes assigned in the tag (like if you have "), you can see my loophole around this for the background color.
We have been developing internally this library https://github.com/square1-io/rich-text-android for a while now and we use it in a number of content intensive news apps.
The library can parse most common html tags including video and img with remote download of images.
A custom view, RichTextView can then be used as a replacement of TextView to display the parsed content.
We have released it publicly just recently so the doc is still incomplete however the example provided should be easy to follow to see if it fit your needs.
Although I can see it it in Html.java API that style and text-align should be usable with the tags <p> , <div> etc. I am failing to get it to work with <p align="center"> or <p style="text-align: center"> and many other variants. Not being able to do this center alignment of text, and other styles like font size, multiple font faces from my ttf files, background colour, I have made my own htmlTextView based on TextView but with my own tagHandler class. Given one or two minor irritations, most of the tags are fine but my custom alignment tags, left, centre, right work only in special conditions (that I don't understand), otherwise. they don't work or crash the app! This is my alignment tag handle. It has the same structure as all the other custom tag handlers but really behaves weirdly! The basic form of my tag handlers are the same and are not conceived by me! I found the taghandler template after many hours searching on the web. I am grateful to whoever it was that posted it but my memory and organisational ability are such that I can't really remember who or where, so if you recognise this code as yours please let me know. The only link (which is here) I have is in my code: stackoverflow: Android: How to use the Html.TagHandler?
private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, AlignmentSpan.Standard.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
I think the problem is that the end tag is not getting connected the correct start tag.
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
This is the total class and something is not right. The largest component is my understanding! Perhaps someone can help me understand better...
public class htmlTextView extends AppCompatTextView {
static Typeface mLogo;
static Typeface mGAMZ;
static Typeface mChalk;
static Typeface mSouvenir;
int GS_PAINTFLAGS = FILTER_BITMAP_FLAG | ANTI_ALIAS_FLAG | SUBPIXEL_TEXT_FLAG | HINTING_ON;
public htmlTextView(Context context) {
super(context);
initialise();
}
public htmlTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initialise();
}
public htmlTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialise();
}
private void initialise() {
mLogo = Typeface.createFromAsset(theAssetManager, "fonts/logo.ttf");
mGAMZ = Typeface.createFromAsset(theAssetManager, "fonts/GAMZ One.ttf");
mChalk = Typeface.createFromAsset(theAssetManager, "fonts/swapfix.ttf");
mSouvenir = Typeface.createFromAsset(theAssetManager, "fonts/Souvenir Regular.ttf");
setPaintFlags(GS_PAINTFLAGS);
}
public void setDefaultTypefaceSouvenir() {
setTypeface(mSouvenir);
}
public void setDefaultTypefaceGAMZ() {
setTypeface(mGAMZ);
}
public void setDefaultTypefaceChalk() {
setTypeface(mChalk);
}
/*public myTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}*/
public void setHTML(String htmltext) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Nougat API 24
setText(Html.fromHtml(htmltext, Html.FROM_HTML_MODE_LEGACY,
null, new TypefaceTagHandler()));
} else {
setText(Html.fromHtml(htmltext, null, new TypefaceTagHandler()));
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
#Override
public Bitmap getDrawingCache(boolean autoScale) {
return super.getDrawingCache(autoScale);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
// http://stackoverflow.com/questions/4044509/android-how-to-use-the-html-taghandler
private static class TypefaceTagHandler implements Html.TagHandler {
private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, AlignmentSpan.Standard.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessTypefaceTag(Typeface tf, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new CustomTypefaceSpan("", tf), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, CustomTypefaceSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new CustomTypefaceSpan("", tf), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessScaleTag(float scalefactor, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new RelativeSizeSpan(scalefactor), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, RelativeSizeSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new RelativeSizeSpan(scalefactor), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessBox(int colour, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new BackgroundColorSpan(colour), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, BackgroundColorSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new BackgroundColorSpan(colour), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessTextColour(int colour, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new ForegroundColorSpan(colour), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, ForegroundColorSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new ForegroundColorSpan(colour), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
final HashMap<String, String> attributes = new HashMap<>();
#Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
String Attr = "";
//if (!opening) attributes.clear();
processAttributes(xmlReader);
if ("txt".equalsIgnoreCase(tag)) {
Attr = attributes.get("clr");
System.out.println("clr Attr: " + Attr + ", opening: " + opening);
if (Attr == null || Attr.isEmpty()
|| "black".equalsIgnoreCase(Attr)
|| Attr.charAt(0) == 'k') {
System.out.println("did black, opening: " + opening);
ProcessTextColour(parseColor("#000000"), opening, output);
} else {
if (Attr.equalsIgnoreCase("g")) {
ProcessTextColour(parseColor("#b2b3b3"), opening, output);
} else {
System.out.println("did colour, opening: " + opening);
ProcessTextColour(parseColor(Attr), opening, output);
}
}
return;
}
if ("box".equalsIgnoreCase(tag)) {
ProcessBox(parseColor("#d7d6d5"), opening, output);
return;
}
if ("scl".equalsIgnoreCase(tag)) {
Attr = attributes.get("fac");
System.out.println("scl Attr: " + Attr);
if (Attr != null && !Attr.isEmpty()) {
ProcessScaleTag(parseFloat(Attr), opening, output);
}
return;
}
if ("left".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_NORMAL, opening, output);
return;
}
if ("centre".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_CENTER, opening, output);
return;
}
if ("right".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_OPPOSITE, opening, output);
return;
}
if ("logo".equalsIgnoreCase(tag)) {
ProcessTypefaceTag(mLogo, opening, output);
return;
}
if ("gamz".equalsIgnoreCase(tag)) {
ProcessTypefaceTag(mGAMZ, opening, output);
return;
}
if ("chalk".equalsIgnoreCase(tag)) {
System.out.println("chalk " + (opening ? "opening" : "closing"));
ProcessTypefaceTag(mChalk, opening, output);
return;
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
private void processAttributes(final XMLReader xmlReader) {
try {
Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
elementField.setAccessible(true);
Object element = elementField.get(xmlReader);
Field attsField = element.getClass().getDeclaredField("theAtts");
attsField.setAccessible(true);
Object atts = attsField.get(element);
Field dataField = atts.getClass().getDeclaredField("data");
dataField.setAccessible(true);
String[] data = (String[])dataField.get(atts);
Field lengthField = atts.getClass().getDeclaredField("length");
lengthField.setAccessible(true);
int len = (Integer)lengthField.get(atts);
/**
* MSH: Look for supported attributes and add to hash map.
* This is as tight as things can get :)
* The data index is "just" where the keys and values are stored.
*/
for(int i = 0; i < len; i++)
attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
}
catch (Exception e) {
Log.d(TAG, "Exception: " + e);
}
}
}
private static class CustomTypefaceSpan extends TypefaceSpan {
private final Typeface newType;
public CustomTypefaceSpan(String family, Typeface type) {
super(family);
newType = type;
}
#Override
public void updateDrawState(TextPaint ds) {
applyCustomTypeFace(ds, newType);
}
#Override
public void updateMeasureState(TextPaint paint) {
applyCustomTypeFace(paint, newType);
}
private void applyCustomTypeFace(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
}
The htmlTextView is created from the activity with:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
theAssetManager = getAssets();
htmlTextView tv = new htmlTextView(this);
tv.setDefaultTypefaceSouvenir();
tv.setTextColor(BLACK);
tv.setBackgroundColor(0xfff0f0f0);
tv.setPadding(4, 4, 4, 4);
tv.setTextSize(30);
tv.setMovementMethod(new ScrollingMovementMethod());
tv.setHTML(getString(R.string.htmljumblies));
//tv.setHTML(getString(R.string.htmltest));
RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
rl.addView(tv);
}
and htmljumblies is defined in strings.xml as below. This particular version will crash the app but if the first <centre>, </centre> tags are removed from lines 7 and 9, The Jumblies will appear centralised? Confusing and frustrating! Keep them and remove the <centre>, </centre> tags enfolding The Jumblies and nothing happens. The line in the header is not centrally aligned!
<string name="htmljumblies">
<![CDATA[&DoubleLongRightArrow;<logo><scl fac="1.1"><font color="#e5053a">GAMZ</font></scl></logo>
<chalk><scl fac="1.8"> SWAP </scl></chalk>
<scl fac="1.00">Set <b>1</b>, Game <b>1</b></scl>
<br>
<centre>
<gamz><font color="#e5053a"><scl fac="1.50">a</scl></font><scl fac="0.90">(9)</scl></gamz>, <gamz><font color="#00a3dd"><scl fac="1.50">e</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#fba311"><scl fac="1.50">i</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bc5e1e"><scl fac="1.50">o</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bf30b5"><scl fac="1.50">u</scl></font><scl fac="0.90">(9)</scl></gamz>
</centre>
<br>
This is an example of my custom <b>htmlTextView</b> drawn from HTML format
text with custom tags to use custom fonts, colouring typeface sizing and highlight boxes.
The default font is <b><i>Souvenir</i></b>, but 3 other fonts are used:<br>
The <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
<font color="#000080"><gamz><scl fac="0.8"><box>letter</box>
<box>fonts</box><sc></gamz></font>
and <chalk><scl fac="1.8">swapfix</scl></chalk>, essentially
<chalk><scl fac="0.9">Staccato 555</scl></chalk>,
as used in the words <chalk><scl fac="1.2">SWAP</scl></chalk> and
<chalk><scl fac="1.2">FIX</scl></chalk>
on the <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
boxes.
<br>
<centre>
<scl fac="2"><box><b> <u>The Jumblies</u> </b></box></scl><br>
<font color="#0000ff">
They went to sea in a Sieve, they did,<br>
In a Sieve they went to sea:<br>
In spite of all their friends could say,<br>
On a winter\'s morn, on a stormy day,<br>
In a Sieve they went to sea!<br>
And when the Sieve turned round and round,<br>
And every one cried, \'You\'ll all be drowned!\'<br>
They called aloud, \'Our Sieve ain\'t big,<br>
But we don\'t care a button! we don\'t care a fig!<br>
In a Sieve we\'ll go to sea!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
They sailed away in a Sieve, they did,<br>
In a Sieve they sailed so fast,<br>
With only a beautiful pea-green veil<br>
Tied with a riband by way of a sail,<br>
To a small tobacco-pipe mast;<br>
And every one said, who saw them go,<br>
\'O won\'t they be soon upset, you know!<br>
For the sky is dark, and the voyage is long,<br>
And happen what may, it\'s extremely wrong<br>
In a Sieve to sail so fast!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
The water it soon came in, it did,<br>
The water it soon came in;<br>
So to keep them dry, they wrapped their feet<br>
In a pinky paper all folded neat,<br>
And they fastened it down with a pin.<br>
And they passed the night in a crockery-jar,<br>
And each of them said, \'How wise we are!<br>
Though the sky be dark, and the voyage be long,<br>
Yet we never can think we were rash or wrong,<br>
While round in our Sieve we spin!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
And all night long they sailed away;<br>
And when the sun went down,<br>
They whistled and warbled a moony song<br>
To the echoing sound of a coppery gong,<br>
In the shade of the mountains brown.<br>
\'O Timballo! How happy we are,<br>
When we live in a Sieve and a crockery-jar,<br>
And all night long in the moonlight pale,<br>
We sail away with a pea-green sail,<br>
In the shade of the mountains brown!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
They sailed to the Western Sea, they did,<br>
To a land all covered with trees,<br>
And they bought an Owl, and a useful Cart,<br>
And a pound of Rice, and a Cranberry Tart,<br>
And a hive of silvery Bees.<br>
And they bought a Pig, and some green Jack-daws,<br>
And a lovely Monkey with lollipop paws,<br>
And forty bottles of Ring-Bo-Ree,<br>
And no end of Stilton Cheese.<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
And in twenty years they all came back,<br>
In twenty years or more,<br>
And every one said, \'How tall they\'ve grown!<br>
For they\'ve been to the Lakes, and the Torrible Zone,<br>
And the hills of the Chankly Bore!\'<br>
And they drank their health, and gave them a feast<br>
Of dumplings made of beautiful yeast;<br>
And every one said, \'If we only live,<br>
We too will go to sea in a Sieve,---<br>
To the hills of the Chankly Bore!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.</centre></font>
]]>
</string>