i'm using BulletSpan which is customized.
i want to display long text that has '\n'.
every lines are fine except for the text line which has '\n'.
bulleetspan can't apply the indent to the newline text.
this is the result.
and the last text is one text. and the text has '\n' inside.
and the code is..
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val source = listOf("Spans are powerful markup objects that you can use to style text at a character or paragraph level.",
"By attaching spans to text objects, you can change text in a variety of ways, ",
"including adding color, making the text clickable,\scaling the text size,\nand drawing text in a customized way.")
val sb = SpannableStringBuilder()
for (i in source.indices) {
val length = sb.length
sb.append(source[i])
sb.append("\n")
sb.setSpan(CustomBulletSpan(
bulletRadius = dip(8),
gapWidth = dip(14),
mColor = color(),
mWantColor = true
), length, length + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
}
binding.tvResult.text = sb
}
private fun dip(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
resources.displayMetrics
).toInt()
}
private fun color(): Int {
return ContextCompat.getColor(applicationContext, R.color.gray);
}
}
and the customBullentSpan code is..
class CustomBulletSpan(
val bulletRadius: Int = STANDARD_BULLET_RADIUS,
val gapWidth: Int = STANDARD_GAP_WIDTH,
val mColor: Int = STANDARD_COLOR,
val mWantColor: Boolean = false
) : LeadingMarginSpan {
companion object {
// Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
private const val STANDARD_BULLET_RADIUS = 4
private const val STANDARD_GAP_WIDTH = 2
private const val STANDARD_COLOR = 0
}
private var mBulletPath: Path? = null
override fun getLeadingMargin(first: Boolean): Int {
return 2 * bulletRadius + gapWidth
}
override fun drawLeadingMargin(
c: Canvas,
p: Paint,
x: Int,
dir: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
first: Boolean,
layout: Layout?
) {
if ((text as Spanned).getSpanStart(this) == start) {
val style = p.style
p.style = Paint.Style.FILL
var oldColor = 0
if (mWantColor) {
oldColor = p.color
p.color = mColor
}
val yPosition = if (layout != null) {
val line = layout.getLineForOffset(start)
layout.getLineBaseline(line).toFloat() - bulletRadius * 1.3f
} else {
(top + bottom) / 1.3f
}
val xPosition = (x + dir * bulletRadius).toFloat()
if (c.isHardwareAccelerated) {
if (mBulletPath == null) {
mBulletPath = Path()
mBulletPath!!.addCircle(0.0f, 0.0f, bulletRadius.toFloat(), Path.Direction.CW)
}
c.save()
c.translate(xPosition, yPosition)
c.drawPath(mBulletPath!!, p)
c.restore()
} else {
c.drawCircle(xPosition, yPosition, bulletRadius.toFloat(), p)
}
if (mWantColor) {
p.color = oldColor
}
p.style = style
}
}
}
how can i solve this problem??
You could just get the string and split by \n and apply span
var len = 0
for (i in source.indices) {
if (source[i].contains("\n")) {
val splitted = source[i].split("\n")
for (k in splitted.indices) {
len = sb.length
sb.append(splitted[k])
sb.append("\n")
sb.setSpan(
CustomBulletSpan(
bulletRadius = dip(8),
gapWidth = dip(14),
mColor = color(),
mWantColor = true
), len, len + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
} else {
len = sb.length
sb.append(source[i])
sb.append("\n")
sb.setSpan(
CustomBulletSpan(
bulletRadius = dip(8),
gapWidth = dip(14),
mColor = color(),
mWantColor = true
), len, len + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
The other way is to split and add it to new list and iterate over the same and apply spans
val newList = mutableListOf<String>()
for (item in source) {
if(item.contains("\n")) {
val split = item.split("\n")
for (splitItem in split){
newList.add(splitItem)
}
} else{
newList.add(item)
}
}
Related
I am currently creating a chart to display statistics.
The problem is that on the combinedChart I can't display the lineDataChart
The expected result is this
The second image is the superposition of a barChart and a lineChart but the problem is i can't use properly the scroll mode because it will only scroll on one graph.
Here is the code for the first graph
class GraphCombinedChart (context: Context) : CombinedChart(context, null, 0) {
private var chart: CombinedChart? = null
private fun dtoToBarEntry(floats: List<Float>?) : ArrayList<BarEntry> {
return ArrayList<BarEntry>().apply {
floats?.forEachIndexed { index, fl ->
add(BarEntry((index + 1).toFloat() , fl))
}
}
}
private fun dtoToEntry(floats: List<Float>?) : ArrayList<Entry> {
return ArrayList<Entry>().apply {
floats?.forEachIndexed { index, fl ->
add(Entry((index + 1).toFloat() , fl))
}
}
}
fun setupChart(
values: List<PlayerGraphStatModel>?,
landScapeMode: Boolean = false
) {
val indexPerformanceList = dtoToEntry(values?.map { it.timePlayed.toFloat() }?.plus(values.map { it.timePlayed.toFloat() }))
val timePlayedList = dtoToBarEntry( values?.map { it.indexPerf }?.plus(values.map { it.indexPerf }))
val entryData = LineData(LineDataSet(indexPerformanceList, "").apply {
color = Color.White.hashCode()
setDrawValues(true)
})
val barEntryData = BarData(BarDataSet(timePlayedList, "").apply {
color = Color.AiaRed.hashCode()
setDrawValues(false)
})
val combinedData = CombinedData()
combinedData.setData(barEntryData)
combinedData.setData(entryData)
chart = this
formatChart(combinedData, landScapeMode)
configureXAxis()
configureYAxis()
chart?.renderer = BarChartRenderer(chart, chart?.animator, chart?.viewPortHandler)
}
private fun formatChart(combinedData: CombinedData, landScapeMode: Boolean = false) {
val barWidth = 0.75f
chart?.drawOrder = arrayOf(
DrawOrder.LINE,
DrawOrder.BAR,
DrawOrder.LINE,
)
chart?.data = combinedData
chart?.description?.isEnabled = false
chart?.legend?.isEnabled = false
chart?.barData?.barWidth = barWidth
chart?.animateY(1000)
chart?.isDoubleTapToZoomEnabled = false
chart?.setScaleEnabled(false)
chart?.isHorizontalScrollBarEnabled = true
// barChart?.setVisibleXRangeMaximum(8f)
// barChart?.moveViewToX(0f)
if (!landScapeMode) {
chart?.setVisibleXRangeMaximum(8f)
chart?.moveViewToX(11f)
}
chart?.resetViewPortOffsets()
chart?.resetZoom()
chart?.notifyDataSetChanged()
}
private fun configureXAxis() {
chart?.xAxis?.apply {
setDrawGridLines(false)
setDrawAxisLine(false)
setDrawLabels(true)
position = XAxis.XAxisPosition.BOTTOM
valueFormatter = MyXAxisFormatter()
granularity = 1f
labelRotationAngle = +0f
textColor = Color.White.hashCode()
textSize = 12f
textAlignment = TEXT_ALIGNMENT_CENTER
axisMinimum = data.xMin - 0.75f
axisMaximum = data.xMax + 0.75f
disableScroll()
}
}
private fun configureYAxis() {
chart?.axisRight?.apply {
setDrawGridLines(false)
legend?.isEnabled = true
isEnabled = false
}
chart?.axisLeft?.apply {
setAxisMinValue(0f)
setAxisMaxValue(100f)
valueFormatter = MyLeftAxisFormatter()
setDrawGridLines(true)
setDrawAxisLine(true)
textColor = Color.White.hashCode()
textSize = 14f
}
}
inner class MyXAxisFormatter : IndexAxisValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return "${value.toInt()}"
}
}
inner class MyLeftAxisFormatter : IndexAxisValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return "${value.toInt()}"
}
}
}
I have checked and the data is not empty.
Thank you for your help!
In android TextView, when displaying a json string, would like to be properly formatted and it should have some specified field bold, like:
{
"location": {
"country":"GB",
"weather":[
{
"zip":20202,
"description":"sun",
"temp":"80"
}
]
}
},
To be displayed as
Tried #Zain's solution, seems it does not work (see screenshot below). It is set in a TextView on a Dialog. Indentation is not there.
update: it is a TextView in Dialog:
private fun showFormatedJsonString(context: Context, jsonStr: String) {
val dialog = Dialog(context)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setContentView(R.layout.module_path)
dialog.setCanceledOnTouchOutside(true)
dialog.setCancelable(true)
dialog.findViewById<TextView>(R.id.json_str).apply {
text = jsonStr
}
dialog.findViewById<View>(R.id.root_container).setOnClickListener {
dialog.dismiss()
}
val displayMetrics: DisplayMetrics = context.getResources().getDisplayMetrics()
val dialogWidth = (displayMetrics.widthPixels * 0.85).toInt()
val dialogHeight = (displayMetrics.heightPixels * 0.85).toInt()
dialog.getWindow().setLayout(dialogWidth, dialogHeight)
dialog.show()
}
tried with SpannableStringBuilder, does not work either
//spStrBuilder.color ( Color.CYAN) { append("to be colored") }
spStrBuilder.bold { append("to be bold") }
If you have multiple occurrences of the match you'd do this programmatically and use SpannableString for marking the text with bold
String json = "{ \"location\": { \"country\":\"GB\", \"weather\":[ { \"zip\":20202, \"description\":\"sun\", \"temp\":\"80\" } ] } }";
// List of words to be marked with bold
List<String> boldList = Arrays.asList("country", "zip");
final Spannable spannable = new SpannableString(json);
// Finding match of words in the String
for (String word : boldList) {
int startIndex = json.indexOf(word);
do {
int endIndex = startIndex + word.length();
spannable.setSpan(new StyleSpan(BOLD), startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
startIndex = json.indexOf(word, endIndex);
} while (startIndex != -1);
}
TextView textView = findViewById(R.id.foo);
textView.setText(spannable);
UPDATE
Using the code in the dialog:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sometest8)
val json =
"{ \"location\": { \"country\":\"GB\", \"weather\":[ { \"zip\":20202, \"description\":\"sun\", \"temp\":\"80\" } ] } }"
button.setOnClickListener {
showFormatedJsonString(this, json)
}
}
fun formatBold(json: String): Spannable {
val boldList: List<String> = listOf("country", "zip")
val spannable: Spannable = SpannableString(json)
// Finding match of words in the String
for (word in boldList) {
var startIndex = json.indexOf(word)
do {
val endIndex = startIndex + word.length
spannable.setSpan(
StyleSpan(Typeface.BOLD),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
startIndex = json.indexOf(word, endIndex)
} while (startIndex != -1)
}
return spannable
}
private fun showFormatedJsonString(context: Context, jsonStr: String) {
val dialog = Dialog(context)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setContentView(R.layout.module_path)
dialog.setCanceledOnTouchOutside(true)
dialog.setCancelable(true)
dialog.findViewById<TextView>(R.id.json_str).apply {
text = formatBold(jsonStr)
}
dialog.findViewById<View>(R.id.root_container).setOnClickListener {
dialog.dismiss()
}
val displayMetrics: DisplayMetrics = context.getResources().getDisplayMetrics()
val dialogWidth = (displayMetrics.widthPixels * 0.85).toInt()
val dialogHeight = (displayMetrics.heightPixels * 0.85).toInt()
dialog.getWindow()?.setLayout(dialogWidth, dialogHeight)
dialog.show()
}
}
Preview
You can use HtmlCompat.fromHtml( htmlTextHere, HtmlCompat.FROM_HTML_MODE_LEGACY ) to stylize your text with html tags (In your case <b> tag), but this way You must prepare your text like:
"{\"location\": { <b>\"country\"</b>:"+ object.location.country ...
I was making Count-up Timer.
It works well when I press pause button or stop button, but the app freezes when I press start button.
I suspected that I was stuck in while loop but algorithm doesn't reach the codes before while loop.
What is wrong in my codes?
class MainActivity( var second : Int = 0 , var minute : Int = 0 , var hour : Int = 0 , var ongoing : Boolean = false ) : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initLayout()
}
private fun initLayout(): Unit {
val startButton: Button = findViewById(R.id.startButton)
val pauseButton: Button = findViewById(R.id.pauseButton)
val stopButton: Button = findViewById(R.id.stopButton)
var realTime : TextView = findViewById(R.id.Realtime)
startButton.setOnClickListener {
realTime.text = "0"//doesn't reach here
buttonOn()
countTime(realTime)
}
pauseButton.setOnClickListener {
buttonOff()
countTime(realTime)
}
stopButton.setOnClickListener {
buttonOff()
countTime(realTime)
}
}
private fun buttonOn(): Unit {
this.ongoing = true
}
private fun buttonOff(): Unit {
this.ongoing = false
}
private fun showTime(second: Int, minute: Int, hour: Int): String {
var secondShow: String = if (second < 10) "0$second" else second.toString()
var minuteShow: String = if (minute < 10) "0$minute" else minute.toString()
var hourShow: String = if (hour < 10) "0$hour" else hour.toString()
return "$hourShow:$minuteShow:$secondShow"
}
private fun countTime(textView: TextView): Unit {
var text = 0
textView.text = text.toString()
textView.text = showTime(this.second, this.minute, this.hour)
while (this.ongoing) {
Thread.sleep(1_000)
this.second++
if (this.second == 60) {
this.second = 0
this.minute++
if (this.minute == 60) {
this.minute = 0
this.hour++
}
}
textView.text = showTime(this.second, this.minute, this.hour)
//buttonOff reach here
}
}
}
Now i am writing my small molar mass calculator and i can't fix one bug. In MainActivity.kt i fill array from my .xml file, after that i use Regex to parse user input. BUT if i type, for example "C" (carbon) in my program it doesn't recognize it. WHY?
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = MoleculeAdapter(this)
moleculeView.layoutManager = LinearLayoutManager(this)
moleculeView.setHasFixedSize(true)
moleculeView.adapter = adapter
val parser = resources.getXml(R.xml.elements)
val elements = Array(127) { Element() }
thread {
var i = 0
while (parser.eventType != END_DOCUMENT) {
if (parser.eventType == START_TAG && parser.name == "element") {
elements[i].number = parser.getAttributeIntValue(null, "number", 0)
elements[i].letter = parser.getAttributeValue(null, "letter")
elements[i].name = parser.getAttributeValue(null, "name")
val weight = parser.getAttributeValue(null, "weight")
elements[i].weight = if (weight.isNotEmpty()) weight.toFloat() else 0F
i++
}
parser.next()
}
parser.close()
}.join()
Log.i("elements:", elements.joinToString { it.toString() + "\n" })
val lowerCaseLetters = "abcdefghiklmnopqrstuy"
val elementsRegex = Regex("""[ABCDEFGHIKLMNOPRSTUVWXYZ]([$lowerCaseLetters]{2}|[$lowerCaseLetters]?)\d*""")
val digitsRegex = Regex("""\d+""")
formulaInput.doOnTextChanged { text, _, _, _ ->
lateinit var foundedElements: List<Element>
thread {
foundedElements = elementsRegex
.findAll(text ?: "")
.map {
elements.find { element ->
Log.i("value", it.value + " " + it.value)
if (it.value.filter { it.isLetter() } == element.letter) {
val number = digitsRegex.find(it.value)
if (number != null) {
try {
element.moleculeCount = number.value.toInt()
element.weight = element.weight * number.value.toInt()
} catch (e: NumberFormatException) { }
}
element.percentage = adapter.getTotalWeight(element.weight) * 100
true
} else false
}
}.filterNotNull().toList()
}.join()
adapter.insertElements(foundedElements)
}
}
}
Element.kt:
data class Element(var number: Int = -1,
var letter: String = "",
var name: String = "",
var weight: Float = 0F,
var percentage: Float = 100F,
var moleculeCount: Int = 1)
xml file item example:
<element
number="6"
letter="С"
name="Углерод"
weight="12.011" />
I can't believe it, in my xml file letter "С" was a cyrillic letter "C" (\u0421)! And because of this equals check "С" == "C" was failing.
Huge Thanks to Wiktor Stribiżew for his comment.
Why not help me any people???
In my application i want show timer in per items of recyclerView !
I write below codes with Kotlin language, but when scroll recyclerView timers reseted and start again from startTime not continue it!
My problem is timer reset, i want when scroll on recyclerView items, timers not reseted and i want continue timer!
My adapter codes:
class AuctionsTodayAdapter(val context: Context, val model: MutableList<AuctionsTodayResponse.Res.Today>) :
RecyclerView.Adapter<AuctionsTodayAdapter.MyHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
val view = LayoutInflater.from(context).inflate(R.layout.row_bidzila_list, parent, false)
return MyHolder(view)
}
override fun getItemCount(): Int {
return model.size
}
override fun onBindViewHolder(holder: MyHolder, position: Int) {
val modelUse = model[position]
holder.setData(modelUse)
}
override fun onViewAttachedToWindow(holder: MyHolder) {
val pos = holder.adapterPosition
val modelUse = model[pos]
holder.handler.post { holder.timer(modelUse.calculateEnd, 1000) }
}
override fun onViewDetachedFromWindow(holder: MyHolder) {
if (holder.timmer != null) {
holder.timmer.cancel()
}
}
inner class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var handler = Handler(Looper.getMainLooper())
lateinit var timmer: CountDownTimer
fun setData(model: AuctionsTodayResponse.Res.Today) {
model.image.let {
Glide.with(context)
.load(Constants.BIDZILA_BASE_URL + it)
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
.into(itemView.rowBidzila_img)
}
model.title.let { itemView.rowBidzila_title.text = it }
}
fun timer(millisInFuture: Long, countDownInterval: Long): CountDownTimer {
timmer = object : CountDownTimer(millisInFuture * 1000, countDownInterval) {
override fun onTick(millisUntilFinished: Long) {
var seconds = (millisUntilFinished / 1000).toInt()
val hours = seconds / (60 * 60)
val tempMint = seconds - hours * 60 * 60
val minutes = tempMint / 60
seconds = tempMint - minutes * 60
itemView.rowBidzila_timer.rowBidzila_timer.text =
String.format("%02d", hours) + ":" + String.format(
"%02d",
minutes
) + ":" + String.format("%02d", seconds)
}
override fun onFinish() {
itemView.rowBidzila_timer.rowBidzila_timer.text = "Finished"
}
}.start()
return timmer
}
}
}
My models :
data class Today(
#SerializedName("auction_status")
val auctionStatus: String = "", // accept
#SerializedName("base_price")
val basePrice: Int = 0, // 120000
#SerializedName("bid_number")
val bidNumber: Int = 0, // 1
#SerializedName("calculate_end")
var calculateEnd: Long = 0, // -9815
#SerializedName("can_offer_before")
val canOfferBefore: Int = 0, // 1
#SerializedName("capacity")
val capacity: Int = 0, // 25
#SerializedName("current_price")
val currentPrice: Int = 0, // 224000
#SerializedName("discount")
val discount: Int = 0, // 400000
#SerializedName("end")
val end: Int = 0, // 1548650312
#SerializedName("end_date")
val endDate: String = "", // 2019-01-28 08:08:32
#SerializedName("end_time")
val endTime: String = "", // 2019-01-28 08:08:32
#SerializedName("final_discount")
val finalDiscount: Int = 0, // 176000
#SerializedName("final_price")
val finalPrice: Int = 0, // 224000
#SerializedName("id")
val id: Int = 0, // 2629
#SerializedName("image")
val image: String = "", // /img/product/5bd94ed3cb9d2.png
#SerializedName("lastbid")
val lastbid: Any = Any(), // null
#SerializedName("live")
val live: String = "",
#SerializedName("max_price")
val maxPrice: Int = 0, // 240000
#SerializedName("max_price_percent")
val maxPricePercent: Int = 0, // 60
#SerializedName("name")
val name: String = "",
#SerializedName("number_of_users")
val numberOfUsers: Int = 0, // 14
#SerializedName("order")
val order: Int = 0, // 0
#SerializedName("price")
val price: Int = 0, // 400000
#SerializedName("product_id")
val productId: Int = 0, // 671
#SerializedName("registered")
val registered: String = "", // null
#SerializedName("registereduser")
val registereduser: Int = 0, // 14
#SerializedName("second_image")
val secondImage: String = "",
#SerializedName("start_date")
val startDate: String = "", // 2019-01-28 08:00:00
#SerializedName("tag")
val tag: String = "",
#SerializedName("tag_color")
val tagColor: String = "", // ff3232
#SerializedName("tag_description")
val tagDescription: String = "",
#SerializedName("title")
val title: String = "",
#SerializedName("winner_avatar")
val winnerAvatar: String = "", // /img/avatar/009-social-11.png
#SerializedName("winner_id")
val winnerId: Int = 0, // 57582
#SerializedName("winner_name")
val winnerName: String = "", // Arashr1
val running: Boolean = true,
var thread: Thread
) {
init {
thread = Thread(Runnable {
while (running) {
calculateEnd--
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
})
thread.start()
}
}
How can i fix it?
You can use a model. And in this model you can set last counted value. and in your ViewHolder you will start time from last counted value from your model. I hope you understand