Custom ItemDecoration:
class DemoDecoration(context: Context, left: Float, top: Float, right: Float, bottom: Float) : ItemDecoration() {
companion object {
private const val dividerHeight = 8f
private const val dividerPaddingLeft = 0f
}
private val left: Int
private val top: Int
private val right: Int
private val bottom: Int
private val dividerPaddingRight = 0f
private val dividerPaint: Paint
private val textPaint: Paint
init {
val density = context.resources.displayMetrics.density
this.left = (density * left).toInt()
this.top = (density * top).toInt()
this.right = (density * right).toInt()
this.bottom = (density * bottom).toInt()
dividerPaint = Paint()
dividerPaint.isAntiAlias = true
dividerPaint.style = Paint.Style.FILL
dividerPaint.color = ContextCompat.getColor(context, android.R.color.darker_gray)
textPaint = Paint()
textPaint.isAntiAlias = true
textPaint.style = Paint.Style.FILL
textPaint.textSize = 30f
textPaint.typeface = Typeface.DEFAULT_BOLD
textPaint.color = Color.BLACK
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
draw(canvas, parent)
}
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
draw(canvas, parent)
}
private fun draw(canvas: Canvas, parent: RecyclerView) {
if (parent.layoutManager == null) {
return
}
canvas.save()
drawRect(canvas, parent)
canvas.restore()
}
private fun drawRect(canvas: Canvas, parent: RecyclerView) {
val childCount = parent.childCount
val spanCount = getSpanCount(parent)
val space = 0f.coerceAtLeast(((top + bottom).toFloat() - dividerHeight) / 2)
var i = 0
if (i < childCount) {
val view = parent.getChildAt(i)
val position = parent.getChildAdapterPosition(view)
if (position / spanCount % 2 == 0) {
canvas.drawRect(dividerPaddingLeft, view.bottom + space,
parent.right - dividerPaddingRight, view.bottom + space + dividerHeight,
dividerPaint)
}
i += spanCount
}
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
if (parent.layoutManager == null) {
return
}
val position = parent.getChildAdapterPosition(view)
val spanCount = getSpanCount(parent)
val index = position / spanCount
if (index > 0 && index % 2 == 0) {
outRect[left, 56, right] = bottom
} else {
outRect[left, top, right] = bottom
}
}
private fun getSpanCount(parent: RecyclerView): Int {
var spanCount = -1
val layoutManager = parent.layoutManager
if (layoutManager is GridLayoutManager) {
spanCount = layoutManager.spanCount
} else if (layoutManager is StaggeredGridLayoutManager) {
spanCount = layoutManager.spanCount
}
return spanCount
}
}
change item position
class MainActivity: AppCompatActivity() {
private lateinit var change: Button
private lateinit var recycler: RecyclerView
private val demoAdapter: DemoAdapter = DemoAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
change = findViewById(R.id.change)
change.setOnClickListener {
val list = ArrayList(demoAdapter.currentList)
list.add(10, list.removeAt(5))
demoAdapter.submitList(list)
}
recycler = findViewById(R.id.recycler)
recycler.apply {
layoutManager = GridLayoutManager(this#MainActivity, 5)
setHasFixedSize(true)
isNestedScrollingEnabled = false
itemAnimator = DefaultItemAnimator()
addItemDecoration(DemoDecoration(this#MainActivity, 2f, 4f, 2f, 4f))
adapter = demoAdapter
}
val data: MutableList<Model> = ArrayList()
for (i: Int in 0 .. 39) {
when ((1..2).random()) {
1 -> data.add(Model("$i\n\na"))
2 -> data.add(Model("$i\na\nb"))
}
}
demoAdapter.submitList(data)
}
}
adapter
class DemoAdapter: ListAdapter<Model, DemoAdapter.ViewHolder>(TaskDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val model = getItem(position)
holder.text.text = model.text
}
class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
val container: ConstraintLayout
val text: TextView
init {
container = view.findViewById(R.id.container)
text = view.findViewById(R.id.text)
}
}
private class TaskDiffCallback : DiffUtil.ItemCallback<Model>() {
override fun areItemsTheSame(oldItem: Model, newItem: Model): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Model, newItem: Model): Boolean {
return oldItem == newItem
}
}
}
data class Model(val text: String)
item layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/shape_stroke_black">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:background="#android:color/holo_blue_light"
android:textColor="#android:color/black"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecyclerviewDemo
5-->10
Item height is changed. How can to keep item height unchanged?
You can fix the size of the items you create for the recyclerview. Try to give the average size of the incoming data as width height in the design. or in html logic : width: 100vw;
height: 100vh; can you try
Related
I have a recyclerview where view type is edittext and on value entered of the edittext, other edittext values should be added with current value.
value entered in edittext item 1 of recyclerview and value entered in edittext item 2 should be the sum in the edittext item 3 which is not editable.
So in below code, the type 'Edittext' is used to identify that these edittext have text watchers and on change of these editttext values, the values of calculations be set in else part.
How to update the data in else code when i am updating the text in editable edittext. this is my code below.
Anyone having solution,
// enter code here
class ProfileDataSubAdapter(
val context: Context,
var repeatedDataList: RealmList<EntityDetails>
) : RecyclerView.Adapter<ProfileDataSubAdapter.DataAdapterViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): DataAdapterViewHolder {
val layout = when (viewType) {
TYPE_EDITTEXT -> R.layout.item_edittext
TYPE_SPINNER -> R.layout.item_spinner
TYPE_CALCULATE -> R.layout.item_edittext
else -> throw IllegalArgumentException("Invalid view type")
}
val view = LayoutInflater
.from(context)
.inflate(layout, parent, false)
return DataAdapterViewHolder(view)
}
override fun onBindViewHolder(holder: DataAdapterViewHolder, position: Int) {
holder.bind(repeatedDataList[position], context, position)
}
fun getRepeatedList(): RealmList<EntityDetails>? {
repeatedDataList.forEach {
if (it.ValueType.equals("EditText") && it.UserEnterValue?.isNullOrEmpty()!!) {
Toast.makeText(context, "Field ${it.EntityName} is empty", Toast.LENGTH_LONG).show()
return null;
}
}
return repeatedDataList
}
override fun getItemCount(): Int = repeatedDataList.size
override fun getItemViewType(position: Int): Int {
if (repeatedDataList[position]?.ValueType == "EditText") {
return TYPE_EDITTEXT
} else if (repeatedDataList[position]?.ValueType == "RadioButton") {
return TYPE_SPINNER
} else
return TYPE_CALCULATE
}
companion object {
private const val TYPE_EDITTEXT = 0
private const val TYPE_SPINNER = 1
private const val TYPE_CALCULATE = 3
}
inner class DataAdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindEdittextData(item: EntityDetails, position: Int) {
//Do your view assignment here from the data model
itemView.findViewById<TextView>(R.id.tv_edit_text_label)?.text = item.EntityName
val content = itemView.findViewById<EditText>(R.id.et_content)
if (item.UserEnterValue?.isNotEmpty()!!) {
content.setText(item.UserEnterValue)
}
//if type is edittext then set text watchers here
if (item.ValueType == "EditText") {
content.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
repeatedDataList[position]?.UserEnterValue = p0.toString()
}
override fun afterTextChanged(p0: Editable?) {
}
})
}
// else if type is calculation set the total here
else {
content.setInputType(InputType.TYPE_NULL)
}
}
fun bindSpinnerData(item: EntityDetails, context: Context) {
//Do your view assignment here from the data model
itemView.findViewById<TextView>(R.id.tv_spinner_label)?.text = item.EntityName
val spinner = itemView.findViewById<Spinner>(R.id.spinner_content)
spinner.adapter = ArrayAdapter<String>(
context,
R.layout.row_spinner,
R.id.txt_content,
item.Values?.map { it.Value } as MutableList<String>
)
// spnr_house.setSelection(getIndexSpinner(spnr_house, noOfYearsInCurrentHouse?.trim()))
spinner.setSelection(item.Values!!.indexOfFirst {
it.IsSelected == true
}, false)
// spinner.setSelection()
spinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(view: AdapterView<*>?) {
}
override fun onItemSelected(
parentView: AdapterView<*>?,
selectedView: View?,
pos: Int,
id: Long
) {
val selectedItem =
spinner.getItemAtPosition(pos)
.toString()
if (selectedItem != "Select") {
item.Values?.find { it.Value == selectedItem }?.IsSelected =
true
val list = item.Values?.filter { it.Value != selectedItem }
list?.forEach { it.IsSelected = false }
}
}
}
}
fun bind(
dataModel: EntityDetails?,
context: Context,
position: Int
) {
if (dataModel?.ValueType == "EditText" || dataModel?.ValueType == "Calculation")
bindEdittextData(dataModel, position)
else if (dataModel?.ValueType == "RadioButton")
bindSpinnerData(dataModel, context)
}
}
}
//end code
I am using CameraX(QuickStart CameraX Basic from GitHub) and firebase ML kit for live face detection. Everything works fine except bounding box is not being drawn on the face but away from the face.
I am using ML kit quickStart GraphicOverlay.Class and FaceGraphic.class.
GraphicOverlay.java
public class GraphicOverlay extends View {
private final Object mLock = new Object();
private int mPreviewWidth;
private float mWidthScaleFactor = 1.5f;
private int mPreviewHeight;
private float mHeightScaleFactor = 1.5f;
private int mFacing = CameraSource.CAMERA_FACING_BACK;
private Set<Graphic> mGraphics = new HashSet<>();
public GraphicOverlay(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void clear() {
synchronized (mLock) {
mGraphics.clear();
}
postInvalidate();
}
public void add(Graphic graphic) {
synchronized (mLock) {
mGraphics.add(graphic);
}
postInvalidate();
}
public void remove(Graphic graphic) {
synchronized (mLock) {
mGraphics.remove(graphic);
}
postInvalidate();
}
public void setCameraInfo(int previewWidth, int previewHeight, int facing) {
synchronized (mLock) {
mPreviewWidth = previewWidth;
mPreviewHeight = previewHeight;
mFacing = facing;
}
postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
synchronized (mLock) {
if ((mPreviewWidth != 0) && (mPreviewHeight != 0)) {
mWidthScaleFactor = (float) getWidth() / (float) mPreviewWidth;
mHeightScaleFactor = (float)getHeight() / (float) mPreviewHeight;
}
for (Graphic graphic : mGraphics) {
graphic.draw(canvas);
}
}
}
public static abstract class Graphic {
private GraphicOverlay mOverlay;
public Graphic(GraphicOverlay overlay) {
mOverlay = overlay;
}
public abstract void draw(Canvas canvas);
public float scaleX(float horizontal) {
return horizontal * mOverlay.mWidthScaleFactor;
}
public float scaleY(float vertical) {
return vertical * mOverlay.mHeightScaleFactor;
}
public float translateX(float x) {
if (mOverlay.mFacing == CameraSource.CAMERA_FACING_FRONT) {
return mOverlay.getWidth() - scaleX(x);
} else {
return scaleX(x);
}
}
public float translateY(float y) {
return scaleY(y);
}
public void postInvalidate() {
mOverlay.postInvalidate();
}
}
}
FaceGraphic.java
public class FaceGraphic extends GraphicOverlay.Graphic {
private static final float FACE_POSITION_RADIUS = 10.0f;
private static final float ID_TEXT_SIZE = 40.0f;
private static final float ID_Y_OFFSET = 50.0f;
private static final float ID_X_OFFSET = -50.0f;
private static final float BOX_STROKE_WIDTH = 5.0f;
private static final int[] COLOR_CHOICES = {
Color.BLUE //, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.RED, Color.WHITE, Color.YELLOW
};
private static int currentColorIndex = 0;
private final Paint facePositionPaint;
private final Paint idPaint, centerPoint;
private final Paint boxPaint, screenCenterPaint;
private final Paint movePaint;
GraphicOverlay graphicOverlay;
private int facing;
private volatile FirebaseVisionFace firebaseVisionFace;
public FaceGraphic(GraphicOverlay overlay) {
super(overlay);
this.graphicOverlay = overlay;
currentColorIndex = (currentColorIndex + 1) % COLOR_CHOICES.length;
final int selectedColor = COLOR_CHOICES[currentColorIndex];
screenCenterPaint = new Paint();
screenCenterPaint.setColor(Color.GREEN);
facePositionPaint = new Paint();
facePositionPaint.setColor(selectedColor);
idPaint = new Paint();
idPaint.setColor(Color.WHITE);
idPaint.setTextSize(ID_TEXT_SIZE);
boxPaint = new Paint();
boxPaint.setColor(Color.WHITE);
boxPaint.setStyle(Paint.Style.STROKE);
boxPaint.setStrokeWidth(BOX_STROKE_WIDTH);
centerPoint = new Paint();
centerPoint.setStrokeWidth(5f);
centerPoint.setColor(Color.RED);
centerPoint.setStyle(Paint.Style.STROKE);
movePaint = new Paint();
movePaint.setColor(Color.RED);
movePaint.setTextSize(38);
}
public void updateFace(FirebaseVisionFace face, int facing) {
firebaseVisionFace = face;
this.facing = facing;
postInvalidate();
}
#Override
public void draw(Canvas canvas) {
canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, 10, screenCenterPaint);
FirebaseVisionFace face = firebaseVisionFace;
if (face == null) {
return;
}
// Draws a circle at the position of the detected face, with the face's track id below.
float x = translateX(face.getBoundingBox().centerX());
float y = translateY(face.getBoundingBox().centerY());
canvas.drawCircle(x, y, FACE_POSITION_RADIUS, facePositionPaint);
Log.d("myFaceBounds", String.valueOf(face.getBoundingBox()));
float faceRightOrLeftAngle = face.getHeadEulerAngleY();
float faceTiltAngle = face.getHeadEulerAngleZ();
// Draws a bounding box around the face.
float xOffset = scaleX(face.getBoundingBox().width() / 2.0f);
float yOffset = scaleY(face.getBoundingBox().height() / 2.0f);
float left = x - xOffset - 100;
float top = y - yOffset - 100;
float right = x + xOffset + 100;
float bottom = y + yOffset + 100;
canvas.drawRect(left, top, right, bottom, boxPaint);
}
}
CameraFragment.kt
class CameraFragment : Fragment() {
private lateinit var container: FrameLayout
private lateinit var viewFinder: TextureView
private lateinit var outputDirectory: File
private lateinit var broadcastManager: LocalBroadcastManager
private var displayId = -1
private var lensFacing = CameraX.LensFacing.BACK
private var preview: Preview? = null
private var imageCapture: ImageCapture? = null
private var imageAnalyzer: ImageAnalysis? = null
private lateinit var graphicOverlay: GraphicOverlay
private val volumeDownReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val keyCode = intent.getIntExtra(KEY_EVENT_EXTRA, KeyEvent.KEYCODE_UNKNOWN)
when (keyCode) {
// When the volume down button is pressed, simulate a shutter button click
KeyEvent.KEYCODE_VOLUME_DOWN -> {
val shutter = container
.findViewById<ImageButton>(R.id.camera_capture_button)
shutter.simulateClick()
}
}
}
}
private val analyzerThread = HandlerThread("LuminosityAnalysis").apply { start() }
private lateinit var displayManager: DisplayManager
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
override fun onDisplayChanged(displayId: Int) = view?.let { view ->
if (displayId == this#CameraFragment.displayId) {
Log.d(TAG, "Rotation changed: ${view.display.rotation}")
preview?.setTargetRotation(view.display.rotation)
imageCapture?.setTargetRotation(view.display.rotation)
imageAnalyzer?.setTargetRotation(view.display.rotation)
}
} ?: Unit
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
override fun onResume() {
super.onResume()
if (!PermissionsFragment.hasPermissions(requireContext())) {
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
CameraFragmentDirections.actionCameraToPermissions())
}
}
override fun onDestroyView() {
super.onDestroyView()
broadcastManager.unregisterReceiver(volumeDownReceiver)
displayManager.unregisterDisplayListener(displayListener)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_camera, container, false)
private fun setGalleryThumbnail(file: File) {
val thumbnail = container.findViewById<ImageButton>(R.id.photo_view_button)
thumbnail.post {
thumbnail.setPadding(resources.getDimension(R.dimen.stroke_small).toInt())
Glide.with(thumbnail)
.load(file)
.apply(RequestOptions.circleCropTransform())
.into(thumbnail)
}
}
private val imageSavedListener = object : ImageCapture.OnImageSavedListener {
override fun onError(
error: ImageCapture.UseCaseError, message: String, exc: Throwable?) {
Log.e(TAG, "Photo capture failed: $message")
exc?.printStackTrace()
}
override fun onImageSaved(photoFile: File) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setGalleryThumbnail(photoFile)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
requireActivity().sendBroadcast(
Intent(Camera.ACTION_NEW_PICTURE, Uri.fromFile(photoFile)))
}
val mimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(photoFile.extension)
MediaScannerConnection.scanFile(
context, arrayOf(photoFile.absolutePath), arrayOf(mimeType), null)
}
}
/*---------------------------------------------------------------------------------------------------------------------------------------------*/
#SuppressLint("MissingPermission")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
container = view as FrameLayout
viewFinder = container.findViewById(R.id.view_finder)
graphicOverlay = container.findViewById(R.id.graphicOverlay)
broadcastManager = LocalBroadcastManager.getInstance(view.context)
val filter = IntentFilter().apply { addAction(KEY_EVENT_ACTION) }
broadcastManager.registerReceiver(volumeDownReceiver, filter)
displayManager = viewFinder.context
.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.registerDisplayListener(displayListener, null)
outputDirectory = MainActivity.getOutputDirectory(requireContext())
viewFinder.post {
displayId = viewFinder.display.displayId
updateCameraUi()
bindCameraUseCases()
lifecycleScope.launch(Dispatchers.IO) {
outputDirectory.listFiles { file ->
EXTENSION_WHITELIST.contains(file.extension.toUpperCase())
}.sorted().reversed().firstOrNull()?.let { setGalleryThumbnail(it) }
}
}
}
private fun bindCameraUseCases() {
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
val viewFinderConfig = PreviewConfig.Builder().apply {
setLensFacing(lensFacing)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(viewFinder.display.rotation)
}.build()
preview = AutoFitPreviewBuilder.build(viewFinderConfig, viewFinder)
graphicOverlay.setCameraInfo(metrics.widthPixels, metrics.heightPixels, getLensFacing())
val imageCaptureConfig = ImageCaptureConfig.Builder().apply {
setLensFacing(lensFacing)
setCaptureMode(CaptureMode.MIN_LATENCY)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(viewFinder.display.rotation)
}.build()
imageCapture = ImageCapture(imageCaptureConfig)
// Setup image analysis pipeline that computes average pixel luminance in real time
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setLensFacing(lensFacing)
setCallbackHandler(Handler(analyzerThread.looper))
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
setTargetRotation(viewFinder.display.rotation)
}.build()
imageAnalyzer = ImageAnalysis(analyzerConfig).apply {
analyzer = LuminosityAnalyzer(graphicOverlay)
}
CameraX.bindToLifecycle(
viewLifecycleOwner, preview, imageCapture, imageAnalyzer)
}
#SuppressLint("RestrictedApi")
private fun updateCameraUi() {
container.findViewById<ConstraintLayout>(R.id.camera_ui_container)?.let {
container.removeView(it)
}
val controls = View.inflate(requireContext(), R.layout.camera_ui_container, container)
controls.findViewById<ImageButton>(R.id.camera_capture_button).setOnClickListener {
imageCapture?.let { imageCapture ->
val photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)
val metadata = Metadata().apply {
isReversedHorizontal = lensFacing == CameraX.LensFacing.FRONT
}
imageCapture.takePicture(photoFile, imageSavedListener, metadata)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
container.postDelayed({
container.foreground = ColorDrawable(Color.WHITE)
container.postDelayed(
{ container.foreground = null }, ANIMATION_FAST_MILLIS)
}, ANIMATION_SLOW_MILLIS)
}
}
}
controls.findViewById<ImageButton>(R.id.camera_switch_button).setOnClickListener {
lensFacing = if (CameraX.LensFacing.FRONT == lensFacing) {
CameraX.LensFacing.BACK
} else {
CameraX.LensFacing.FRONT
}
try {
CameraX.getCameraWithLensFacing(lensFacing)
CameraX.unbindAll()
bindCameraUseCases()
} catch (exc: Exception) {
}
}
controls.findViewById<ImageButton>(R.id.photo_view_button).setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
CameraFragmentDirections.actionCameraToGallery(outputDirectory.absolutePath))
}
}
private class LuminosityAnalyzer(graphicOverlay: GraphicOverlay) : ImageAnalysis.Analyzer {
val graphicOverlay = graphicOverlay
private var lastAnalyzedTimestamp = 0L
private fun getRotation(rotationCompensation: Int): Int {
val result: Int
when (rotationCompensation) {
0 -> result = FirebaseVisionImageMetadata.ROTATION_0
90 -> result = FirebaseVisionImageMetadata.ROTATION_90
180 -> result = FirebaseVisionImageMetadata.ROTATION_180
270 -> result = FirebaseVisionImageMetadata.ROTATION_270
else -> {
result = FirebaseVisionImageMetadata.ROTATION_0
}
}
return result
}
override fun analyze(image: ImageProxy, rotationDegrees: Int) {
val currentTimestamp = System.currentTimeMillis()
if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.MILLISECONDS.toMillis(300)) {
lastAnalyzedTimestamp = currentTimestamp
try {
val y = image.planes[0]
val u = image.planes[1]
val v = image.planes[2]
//Then we can then get the number of pixels in each plane
val Yb = y.buffer.remaining()
val Ub = u.buffer.remaining()
val Vb = v.buffer.remaining()
//and convert them into a single YUV formatted ByteArray
val data = ByteArray(Yb + Ub + Vb)
y.buffer.get(data, 0, Yb)
u.buffer.get(data, Yb, Ub)
v.buffer.get(data, Yb + Ub, Vb)
val metadata = FirebaseVisionImageMetadata.Builder()
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_YV12)
.setHeight(image.height)
.setWidth(image.width)
.setRotation(getRotation(rotationDegrees))
.build()
val options = FirebaseVisionFaceDetectorOptions.Builder()
.setPerformanceMode(FirebaseVisionFaceDetectorOptions.FAST)
.setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
.enableTracking()
.build()
val labelImage = FirebaseVisionImage.fromByteArray(data, metadata)
val detector = FirebaseVision.getInstance().getVisionFaceDetector(options)
detector.detectInImage(labelImage)
.addOnSuccessListener { faces ->
graphicOverlay.clear()
for (face in faces) {
val faceGraphic = FaceGraphic(graphicOverlay)
graphicOverlay.add(faceGraphic)
faceGraphic.updateFace(face, 1)
}
}
.addOnFailureListener { }
} catch (e: IllegalStateException) {
}
}
}
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val PHOTO_EXTENSION = ".jpg"
private fun createFile(baseFolder: File, format: String, extension: String) =
File(baseFolder, SimpleDateFormat(format, Locale.US)
.format(System.currentTimeMillis()) + extension)
}
private fun getLensFacing(): Int {
return if (lensFacing == CameraX.LensFacing.BACK) {
0
} else
1
}
}
AutoFitPreviewBuilder.kt
class AutoFitPreviewBuilder private constructor(
config: PreviewConfig, viewFinderRef: WeakReference<TextureView>) {
/** Public instance of preview use-case which can be used by consumers of this adapter */
val useCase: Preview
/** Internal variable used to keep track of the use case's output rotation */
private var bufferRotation: Int = 0
/** Internal variable used to keep track of the view's rotation */
private var viewFinderRotation: Int? = null
/** Internal variable used to keep track of the use-case's output dimension */
private var bufferDimens: Size = Size(0, 0)
/** Internal variable used to keep track of the view's dimension */
private var viewFinderDimens: Size = Size(0, 0)
/** Internal variable used to keep track of the view's display */
private var viewFinderDisplay: Int = -1
/** Internal reference of the [DisplayManager] */
private lateinit var displayManager: DisplayManager
/**
* We need a display listener for orientation changes that do not trigger a configuration
* change, for example if we choose to override config change in manifest or for 180-degree
* orientation changes.
*/
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
override fun onDisplayChanged(displayId: Int) {
val viewFinder = viewFinderRef.get() ?: return
if (displayId == viewFinderDisplay) {
val display = displayManager.getDisplay(displayId)
val rotation = getDisplaySurfaceRotation(display)
updateTransform(viewFinder, rotation, bufferDimens, viewFinderDimens)
}
}
}
init {
// Make sure that the view finder reference is valid
val viewFinder = viewFinderRef.get() ?:
throw IllegalArgumentException("Invalid reference to view finder used")
// Initialize the display and rotation from texture view information
viewFinderDisplay = viewFinder.display.displayId
viewFinderRotation = getDisplaySurfaceRotation(viewFinder.display) ?: 0
// Initialize public use-case with the given config
useCase = Preview(config)
// Every time the view finder is updated, recompute layout
useCase.onPreviewOutputUpdateListener = Preview.OnPreviewOutputUpdateListener {
val viewFinder =
viewFinderRef.get() ?: return#OnPreviewOutputUpdateListener
Log.d(TAG, "Preview output changed. " +
"Size: ${it.textureSize}. Rotation: ${it.rotationDegrees}")
// To update the SurfaceTexture, we have to remove it and re-add it
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
parent.addView(viewFinder, 0)
// Update internal texture
viewFinder.surfaceTexture = it.surfaceTexture
// Apply relevant transformations
bufferRotation = it.rotationDegrees
val rotation = getDisplaySurfaceRotation(viewFinder.display)
updateTransform(viewFinder, rotation, it.textureSize, viewFinderDimens)
}
// Every time the provided texture view changes, recompute layout
viewFinder.addOnLayoutChangeListener { view, left, top, right, bottom, _, _, _, _ ->
val viewFinder = view as TextureView
val newViewFinderDimens = Size(right - left, bottom - top)
Log.d(TAG, "View finder layout changed. Size: $newViewFinderDimens")
val rotation = getDisplaySurfaceRotation(viewFinder.display)
updateTransform(viewFinder, rotation, bufferDimens, newViewFinderDimens)
}
// Every time the orientation of device changes, recompute layout
// NOTE: This is unnecessary if we listen to display orientation changes in the camera
// fragment and call [Preview.setTargetRotation()] (like we do in this sample), which will
// trigger [Preview.OnPreviewOutputUpdateListener] with a new
// [PreviewOutput.rotationDegrees]. CameraX Preview use case will not rotate the frames for
// us, it will just tell us about the buffer rotation with respect to sensor orientation.
// In this sample, we ignore the buffer rotation and instead look at the view finder's
// rotation every time [updateTransform] is called, which gets triggered by
// [CameraFragment] display listener -- but the approach taken in this sample is not the
// only valid one.
displayManager = viewFinder.context
.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.registerDisplayListener(displayListener, null)
// Remove the display listeners when the view is detached to avoid holding a reference to
// it outside of the Fragment that owns the view.
// NOTE: Even though using a weak reference should take care of this, we still try to avoid
// unnecessary calls to the listener this way.
viewFinder.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View?) =
displayManager.registerDisplayListener(displayListener, null)
override fun onViewDetachedFromWindow(view: View?) =
displayManager.unregisterDisplayListener(displayListener)
})
}
/** Helper function that fits a camera preview into the given [TextureView] */
private fun updateTransform(textureView: TextureView?, rotation: Int?, newBufferDimens: Size,
newViewFinderDimens: Size) {
// This should not happen anyway, but now the linter knows
val textureView = textureView ?: return
if (rotation == viewFinderRotation &&
Objects.equals(newBufferDimens, bufferDimens) &&
Objects.equals(newViewFinderDimens, viewFinderDimens)) {
// Nothing has changed, no need to transform output again
return
}
if (rotation == null) {
// Invalid rotation - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
viewFinderRotation = rotation
}
if (newBufferDimens.width == 0 || newBufferDimens.height == 0) {
// Invalid buffer dimens - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
bufferDimens = newBufferDimens
}
if (newViewFinderDimens.width == 0 || newViewFinderDimens.height == 0) {
// Invalid view finder dimens - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
viewFinderDimens = newViewFinderDimens
}
val matrix = Matrix()
Log.d(TAG, "Applying output transformation.\n" +
"View finder size: $viewFinderDimens.\n" +
"Preview output size: $bufferDimens\n" +
"View finder rotation: $viewFinderRotation\n" +
"Preview output rotation: $bufferRotation")
// Compute the center of the view finder
val centerX = viewFinderDimens.width / 2f
val centerY = viewFinderDimens.height / 2f
// Correct preview output to account for display rotation
matrix.postRotate(-viewFinderRotation!!.toFloat(), centerX, centerY)
// Buffers are rotated relative to the device's 'natural' orientation: swap width and height
val bufferRatio = bufferDimens.height / bufferDimens.width.toFloat()
val scaledWidth: Int
val scaledHeight: Int
// Match longest sides together -- i.e. apply center-crop transformation
if (viewFinderDimens.width > viewFinderDimens.height) {
scaledHeight = viewFinderDimens.width
scaledWidth = Math.round(viewFinderDimens.width * bufferRatio)
} else {
scaledHeight = viewFinderDimens.height
scaledWidth = Math.round(viewFinderDimens.height * bufferRatio)
}
// Compute the relative scale value
val xScale = scaledWidth / viewFinderDimens.width.toFloat()
val yScale = scaledHeight / viewFinderDimens.height.toFloat()
// Scale input buffers to fill the view finder
matrix.preScale(xScale, yScale, centerX, centerY)
// Finally, apply transformations to our TextureView
textureView.setTransform(matrix)
}
companion object {
private val TAG = AutoFitPreviewBuilder::class.java.simpleName
/** Helper function that gets the rotation of a [Display] in degrees */
fun getDisplaySurfaceRotation(display: Display?) = when(display?.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> null
}
/**
* Main entrypoint for users of this class: instantiates the adapter and returns an instance
* of [Preview] which automatically adjusts in size and rotation to compensate for
* config changes.
*/
fun build(config: PreviewConfig, viewFinder: TextureView) =
AutoFitPreviewBuilder(config, WeakReference(viewFinder)).useCase
}
}
I am a newbie on android.
I already made an activity that shows all my contact list and number inside Linear Layout. How can I make phone call using intent after I click the Linear Layout?
I don't know where to put the setOnclickListener in my code. So here I share all of the source-code.
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/listContacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:orientation="vertical">
<TextView
android:id="#+id/itemName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Nama : "
style="#style/Base.TextAppearance.AppCompat.Headline"/>
<TextView
android:id="#+id/itemNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:text="Nomor HP : "
style="#style/Base.TextAppearance.AppCompat.Body2"/>
</LinearLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showDetail.setOnClickListener {
var myDetailIntent = Intent(this, DetailActivity::class.java)
startActivity(myDetailIntent)
}
doAsync {
Thread.sleep(5000L)
uiThread {
showNotivy();
}
}
}
private fun showNotivy() {
val notfyDetailIntent = Intent(this#MainActivity,
DetailActivity::class.java)
val myPendingIntent = TaskStackBuilder.create(this)
.addParentStack(DetailActivity::class.java)
.addNextIntent(notfyDetailIntent)
.getPendingIntent(110, PendingIntent.FLAG_UPDATE_CURRENT)
val myNotfyManager = this.getSystemService(android.content.Context.NOTIFICATION_SERVICE) as NotificationManager
val myBuilder = NotificationCompat.Builder(this)
.setContentTitle("Show Detail Contact")
.setContentText("Click Me !")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(myPendingIntent)
.setAutoCancel(true)
myNotfyManager.notify(1101, myBuilder.build())
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
var DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME
var NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER
val myListContact : MutableList<myContact> = ArrayList();
override fun onCreateLoader(p0: Int, p1: Bundle?): Loader<Cursor> {
val MyContentUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
val myProjection = arrayOf(DISPLAY_NAME, NUMBER)
return CursorLoader(this, MyContentUri, myProjection, null, null, DISPLAY_NAME+ " ASC")
}
override fun onLoadFinished(p0: Loader<Cursor>, p1: Cursor?) {
myListContact.clear()
if (p1!=null) {
p1.moveToFirst()
while(!p1.isAfterLast()) {
myListContact.add(myContact(nama = p1.getString(p1.getColumnIndex(DISPLAY_NAME)),nomorHp = p1.getString(p1.getColumnIndex(NUMBER))))
p1.moveToNext()
}
}
val contactAdapter = myAdapterRecyView(myListContact)
myRecyView.apply {
layoutManager = LinearLayoutManager(this#DetailActivity)
adapter = contactAdapter
}
}
override fun onLoaderReset(p0: Loader<Cursor>) {
val contactAdapter = myAdapterRecyView(myListContact)
myRecyView.apply {
layoutManager = LinearLayoutManager(this#DetailActivity)
adapter = contactAdapter
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
LoaderManager.getInstance(this).initLoader(1, null, this)
}
}
myAdapterRecyView.kt
class myAdapterRecyView(private val contact : List<myContact>): RecyclerView.Adapter<myHolder>() {
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): myHolder {
return myHolder(LayoutInflater.from(p0.context)
.inflate(R.layout.layout_recy_view,p0,false))
}
override fun getItemCount(): Int = contact.size
override fun onBindViewHolder(p0: myHolder, p1: Int) {
p0.bindContact(contact[p1])
}
}
class myHolder(view: View):RecyclerView.ViewHolder(view) {
private val contactName = view.itemName
private val contactNumber = view.itemNumber
fun bindContact(tmp: myContact) {
contactName.text = "${contactName.text} ${tmp.nama}"
contactNumber.text = "${contactNumber.text} ${tmp.nomorHp}"
}
}
class myHolder(view: View):RecyclerView.ViewHolder(view) {
private val contactName = view.itemName
private val contactNumber = view.itemNumber
fun bindContact(tmp: myContact) {
contactName.text = "${contactName.text} ${tmp.nama}"
contactNumber.text = "${contactNumber.text} ${tmp.nomorHp}"
}
}
myContact.kt
class myContact (
val nama : String,
val nomorHp : String
)
You can put yout click action inside the onBind in recycler view adapter.
class myHolder(view: View):RecyclerView.ViewHolder(view) {
private val contactName = view.itemName
private val contactNumber = view.itemNumber
fun bindContact(tmp: myContact) {
contactName.text = "${contactName.text} ${tmp.nama}"
contactNumber.text = "${contactNumber.text} ${tmp.nomorHp}"
view.setOnClickListener {
// YOUR ACTION HERE.
}
}
}
I have a parent layout it has some TextViews and a RecyclerView in it. And the child layout, it has a RecyclerView in it and the adapter has 5 different View Types
For the child recyclerview, i use an Adapter which has 5 different layouts in it that consists of
VIEW_TYPE_LANDSCAPE
VIEW_TYPE_LANDSCAPE_CARD
VIEW_TYPE_PORTRAIT
VIEW_TYPE_SQUARE
VIEW_TYPE_TWO_COLUMN
The problem occurs when the child RecyclerView has more than x items in it. When I scroll down to the bottom of the screen, and then scroll up to the top of the screen and scroll down again to the bottom of the screen, somehow the child RecyclerView shows wrong layout / View Types.
parent_layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="home.HomeTabViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:id="#+id/cl_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="#+id/v_toolbar"
layout="#layout/toolbar_home_v3" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_home_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/v_toolbar" />
</android.support.constraint.ConstraintLayout>
</layout>
child_layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/dimens_32dp">
<TextView
android:id="#+id/tv_title"
style="#style/TextSoftBlackBold.20sp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/dimens_16dp"
android:layout_marginRight="#dimen/dimens_16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:text="Popular Ideas" />
<TextView
android:id="#+id/tv_subtitle"
style="#style/TextDarkGrayNormal.14sp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/dimens_16dp"
android:layout_marginTop="#dimen/dimens_7dp"
android:layout_marginRight="#dimen/dimens_16dp"
android:ellipsize="end"
android:maxLines="2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tv_title"
tools:text="Popular events happening during your stay in Bali" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_menu_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/dimens_2dp"
app:layout_constraintTop_toBottomOf="#+id/tv_subtitle" />
</android.support.constraint.ConstraintLayout>
</layout>
BaseMenuAdapter.kt
class BaseMenuAdapter(val context: Context?,
private var contents: List<HomeViewParam.Content>?,
private val menuGrid: HomeItem.MenuGrid?) :
RecyclerView.Adapter<BaseMenuAdapter.MyViewHolder>() {
companion object {
const val VIEW_TYPE_LANDSCAPE = 0
const val VIEW_TYPE_LANDSCAPE_CARD = 1
const val VIEW_TYPE_PORTRAIT = 2
const val VIEW_TYPE_TWO_COLUMN = 3
const val VIEW_TYPE_SQUARE = 4
}
inner class MyViewHolder : RecyclerView.ViewHolder {
var landscapeBinding: ViewTemplateLandscapeBinding? = null
var landscapeCardBinding: ViewTemplateLandscapeCardBinding? = null
var portraitBinding: ItemHomeMenuPortraitBinding? = null
var twoColumnBinding: ItemHomeMenuTwoColumnBinding? = null
var squareBinding: ItemHomeMenuSquareBinding? = null
constructor(binding: ViewTemplateLandscapeBinding) : super(binding.root) {
landscapeBinding = binding
}
constructor(binding: ViewTemplateLandscapeCardBinding) : super(binding.root) {
landscapeCardBinding = binding
}
constructor(binding: ItemHomeMenuPortraitBinding) : super(binding.root) {
portraitBinding = binding
}
constructor(binding: ItemHomeMenuTwoColumnBinding) : super(binding.root) {
twoColumnBinding = binding
}
constructor(binding: ItemHomeMenuSquareBinding) : super(binding.root) {
squareBinding = binding
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseMenuAdapter.MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ViewDataBinding
return when (viewType) {
0 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.view_template_landscape, parent, false)
MyViewHolder(binding as ViewTemplateLandscapeBinding)
}
1 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.view_template_landscape_card, parent, false)
MyViewHolder(binding as ViewTemplateLandscapeCardBinding)
}
2 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.item_home_menu_portrait, parent, false)
MyViewHolder(binding as ItemHomeMenuPortraitBinding)
}
3 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.item_home_menu_two_column, parent, false)
MyViewHolder(binding as ItemHomeMenuTwoColumnBinding)
}
4 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.item_home_menu_square, parent, false)
MyViewHolder(binding as ItemHomeMenuSquareBinding)
}
else -> {
throw RuntimeException("The type has to be ONE or TWO")
}
}
}
override fun onBindViewHolder(holder: BaseMenuAdapter.MyViewHolder, position: Int) {
context?.resources.run {
when (holder.itemViewType) {
VIEW_TYPE_LANDSCAPE -> {
val binding = holder.landscapeBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_LANDSCAPE_CARD -> {
val binding = holder.landscapeCardBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_TWO_COLUMN -> {
val binding = holder.twoColumnBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_PORTRAIT -> {
val binding = holder.portraitBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_SQUARE -> {
val binding = holder.squareBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
else -> {}
}
}
}
override fun getItemViewType(position: Int): Int {
contents?.let {
return when (menuGrid?.templateType) {
TemplateType.LANDSCAPE -> VIEW_TYPE_LANDSCAPE
TemplateType.LANDSCAPE_CARD -> VIEW_TYPE_LANDSCAPE_CARD
TemplateType.TWO_COLUMN -> VIEW_TYPE_TWO_COLUMN
TemplateType.POTRAIT -> VIEW_TYPE_PORTRAIT
TemplateType.SQUARE -> VIEW_TYPE_SQUARE
else -> -1
}
}
return -1
}
override fun getItemCount(): Int {
return contents?.size ?: 0
}
override fun setHasStableIds(hasStableIds: Boolean) {
super.setHasStableIds(false)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
fun setItems(contents: List<HomeViewParam.Content>?) {
this.contents = contents
}
}
EDIT
Here's my view looks like
So as I've mentioned above, when I scroll up or down, sometimes some items use wrong view type
You need to define one ViewHolder for each of your layout types, and then you need to use each ViewHolder's class instance type to bind the data on the fly, respectively in getItemViewType() and onBindViewHolder(), something along these lines:
#Override
public int getItemViewType(int position) {
if (isValidPosition(position)) {
Data d = mDataset[position];
if (d instanceof LandscapeData) {
return VIEW_TYPE_LANDSCAPE;
} else if (d instanceof PortraitData) {
return VIEW_TYPE_PORTRAIT;
}
// more else-ifs here
}
// default to landscape
return VIEW_TYPE_LANDSCAPE;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Data d = mDataset[position];
if (holder instanceof LandscapeVH) {
// binding of dataset for landscape layouts
LandscapeData data = (LandscapeData) d;
LandscapeVH vh = (LandscapeVH) holder;
// bind the data to the view on the fly here
vh.myTextView.setText(data.getLandscapeTitle());
} else if (holder instanceof PortraitVH) {
// binding of dataset for portrait layouts
PortraitData data = (PortraitData) d;
PortraitVH vh = (PortraitVH) holder;
// bind the data to the view on the fly here
vh.myTextView.setText(data.getPortraitTitle());
}
// more else-ifs here
}
Note: your mDataset[] is your only source of truth, and it contains datasets of type Data which can be subclassed to more specialized datasets like LandscapeData and PortraitData which go hand in hand with your layout's types.
I have a TabLayout with Recyclerview so that when tabs are clicked then the Recyclerview is scrolled to a particular position.
I want the reverse procedure as well such that when the Recyclerview is scrolled to a particular position then the particular tab is highlighted.
For example: If there are 4 tabs in the TabLayout and when Recyclerview is scrolled to 5th position (item visible and below TabLayout) then 3rd tab should be highlighted.
Here when 'How it works' appears below TabLayout then tabs 'How it works' should be highlighted.
Try this
follow this steps
Add a ScrollListener to your RecyclerView
than find first visible item of your RecyclerView
set the select the tab in TabLayout as per position of your RecyclerView
SAMPLE CODE
myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int itemPosition=linearLayoutManager.findFirstCompletelyVisibleItemPosition();
if(itemPosition==0){ // item position of uses
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}else if(itemPosition==1){// item position of side effects
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}else if(itemPosition==2){// item position of how it works
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}else if(itemPosition==3){// item position of precaution
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}
}
});
EDIT
public class MyActivity extends AppCompatActivity {
RecyclerView myRecyclerView;
TabLayout myTabLayout;
LinearLayoutManager linearLayoutManager;
ArrayList<String> arrayList = new ArrayList<>();
DataAdapter adapter;
private boolean isUserScrolling = false;
private boolean isListGoingUp = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
myTabLayout = findViewById(R.id.myTabLayout);
myRecyclerView = findViewById(R.id.myRecyclerView);
linearLayoutManager = new LinearLayoutManager(this);
myRecyclerView.setLayoutManager(linearLayoutManager);
myRecyclerView.setHasFixedSize(true);
for (int i = 0; i < 120; i++) {
arrayList.add("Item " + i);
}
adapter= new DataAdapter(this,arrayList);
myRecyclerView.setAdapter(adapter);
myTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
isUserScrolling = false ;
int position = tab.getPosition();
if(position==0){
myRecyclerView.smoothScrollToPosition(0);
}else if(position==1){
myRecyclerView.smoothScrollToPosition(30);
}else if(position==2){
myRecyclerView.smoothScrollToPosition(60);
}else if(position==3){
myRecyclerView.smoothScrollToPosition(90);
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isUserScrolling = true;
if (isListGoingUp) {
//my recycler view is actually inverted so I have to write this condition instead
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1 == arrayList.size()) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (isListGoingUp) {
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1 == arrayList.size()) {
Toast.makeText(MyActivity.this, "exeute something", Toast.LENGTH_SHORT).show();
}
}
}
}, 50);
//waiting for 50ms because when scrolling down from top, the variable isListGoingUp is still true until the onScrolled method is executed
}
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int itemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if(isUserScrolling){
if (itemPosition == 0) { // item position of uses
TabLayout.Tab tab = myTabLayout.getTabAt(0);
tab.select();
} else if (itemPosition == 30) {// item position of side effects
TabLayout.Tab tab = myTabLayout.getTabAt(1);
tab.select();
} else if (itemPosition == 60) {// item position of how it works
TabLayout.Tab tab = myTabLayout.getTabAt(2);
tab.select();
} else if (itemPosition == 90) {// item position of precaution
TabLayout.Tab tab = myTabLayout.getTabAt(3);
tab.select();
}
}
}
});
}
}
private fun syncTabWithRecyclerView() {
// Move recylerview to the position selected by user
menutablayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
if (!isUserScrolling) {
val position = tab.position
linearLayoutManager.scrollToPositionWithOffset(position, 0)
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
// Detect recyclerview position and select tab respectively.
menuRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isUserScrolling = true
} else if (newState == RecyclerView.SCROLL_STATE_IDLE)
isUserScrolling = false
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (isUserScrolling) {
var itemPosition = 0
if (dy > 0) {
// scrolling up
itemPosition = linearLayoutManager.findLastVisibleItemPosition()
} else {
// scrolling down
itemPosition = linearLayoutManager.findFirstVisibleItemPosition()
}
val tab = menutablayout.getTabAt(itemPosition)
tab?.select()
}
}
})
}
I figured you don't even need those flags and it was enough to override RecyclerView's onScrolled and select tab and scroll to position when tab is selected and that Tab wasn't already selected:
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val llm = recyclerView.layoutManager as LinearLayoutManager
// depending on sections'heights one may want to add more logic
// on how to determine which section to scroll to
val firstCompletePos = llm.findFirstCompletelyVisibleItemPosition()
if (firstCompletePos != tabLayout.selectedTabPosition)
tabLayout.getTabAt(firstCompletePos)?.select()
}
Then I have a TextView which is set as customView to tabLayout:
tabLayout.addTab(newTab().also { tab ->
tab.customView = AppCompatTextView(context).apply {
// set layout params match_parent, so the entire section is clickable
// set style, gravity, text etc.
setOnClickListener {
tabLayout.selectTab(tab)
recyclerView.apply {
val scrollTo = tabLayout.selectedTabPosition
smoothScrollToPosition(scrollTo)
}
}
}
})
With this setup you have:
Tab selected when user both scrolls and flings
RecyclerView is scrolled when user clicks on the tab.
I use information from another answers but here is some omissions in code, that makes it not complete and bad working. My solution 100% work without lags. Photo of complete screen you may see in the end.
This is the code inside Fragment:
private val layoutManager get() = recyclerView?.layoutManager as? LinearLayoutManager
/**
* [SmoothScroller] need for smooth scrolling inside [tabListener] of [recyclerView]
* to top border of [RecyclerView.ViewHolder].
*/
private val smoothScroller: SmoothScroller by lazy {
object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference(): Int = SNAP_TO_START
}
}
/**
* Variable for prevent calling of [RecyclerView.OnScrollListener.onScrolled]
* inside [scrollListener], when user click on [TabLayout.Tab] and
* [tabListener] was called.
*
* Fake calls happens because of [tabListener] have smooth scrolling to position,
* and when [scrollListener] catch scrolling and call [TabLayout.Tab.select].
*/
private var isTabClicked = false
/**
* Variable for prevent calling of [TabLayout.OnTabSelectedListener.onTabSelected]
* inside [tabListener], when user scroll list and function
* [RecyclerView.OnScrollListener.onScrolled] was called inside [scrollListener].
*
* Fake calls happens because [scrollListener] contains call of [TabLayout.Tab.select],
* which in turn calling click handling inside [tabListener].
*/
private var isScrollSelect = false
private val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
/**
* Reset [isTabClicked] key when user start scroll list.
*/
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isTabClicked = false
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
/**
* Prevent scroll handling after tab click (see inside [tabListener]).
*/
if (isTabClicked) return
val commonIndex = commonIndex ?: return
val karaokeIndex = karaokeIndex ?: return
val socialIndex = socialIndex ?: return
val reviewIndex = reviewIndex ?: return
val addIndex = addIndex ?: return
when (layoutManager?.findFirstVisibleItemPosition() ?: return) {
in commonIndex until karaokeIndex -> selectTab(TabIndex.COMMON)
in karaokeIndex until socialIndex -> selectTab(TabIndex.KARAOKE)
in socialIndex until reviewIndex -> {
/**
* In case if [reviewIndex] can't reach top of the list,
* to become first visible item. Need check [addIndex]
* (last element of list) completely visible or not.
*/
if (layoutManager?.findLastCompletelyVisibleItemPosition() != addIndex) {
selectTab(TabIndex.CONTACTS)
} else {
selectTab(TabIndex.REVIEWS)
}
}
in reviewIndex until addIndex -> selectTab(TabIndex.REVIEWS)
}
}
/**
* It's very important to skip cases when [TabLayout.Tab] is checked like current,
* otherwise [tabLayout] will terribly lagging on [recyclerView] scroll.
*/
private fun selectTab(#TabIndex index: Int) {
val tab = tabLayout?.getTabAt(index) ?: return
if (!tab.isSelected) {
recyclerView?.post {
isScrollSelect = true
tab.select()
}
}
}
}
private val tabListener = object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) = scrollToPosition(tab)
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
/*
* If user click on tab again.
*/
override fun onTabReselected(tab: TabLayout.Tab?) = scrollToPosition(tab)
private fun scrollToPosition(tab: TabLayout.Tab?) {
/**
* Prevent scroll to position calling from [scrollListener].
*/
if (isScrollSelect) {
isScrollSelect = false
return
}
val position = when (tab?.position) {
TabIndex.COMMON -> commonIndex
TabIndex.KARAOKE -> karaokeIndex
TabIndex.CONTACTS -> socialIndex
TabIndex.REVIEWS -> reviewIndex
else -> null
}
if (position != null) {
isTabClicked = true
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller)
}
}
}
private val commonIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Info }
private val karaokeIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Karaoke }
private val socialIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Social }
private val reviewIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.ReviewHeader }
private val addIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.AddReview }
Extension:
private const val ND_INDEX = -1
fun <T> List<T>.validIndexOfFirst(predicate: (T) -> Boolean): Int? {
return indexOfFirst(predicate).takeIf { it != ND_INDEX }
}
TabIndex class for getting tab by position:
#IntDef(TabIndex.COMMON, TabIndex.KARAOKE, TabIndex.CONTACTS, TabIndex.REVIEWS)
private annotation class TabIndex {
companion object {
const val COMMON = 0
const val KARAOKE = 1
const val CONTACTS = 2
const val REVIEWS = 3
}
}
And it's how looks my ClubScreenItem:
sealed class ClubScreenItem {
class Info(val data: ClubItem): ClubScreenItem()
...
class Karaoke(...): ClubScreenItem()
class Social(...): ClubScreenItem()
...
class ReviewHeader(...): ClubScreenItem()
...
object AddReview : ClubScreenItem()
}
This is how looks screen:
Try this,
Simple Step :
Detect RecyclerView scroll state
Use findFirstVisibleItemPosition() to return the adapter position of the first visible view
Change the tab based on RecyclerView item position
Done
private fun syncTabWithRecyclerView() {
var isUserScrolling = false
val layoutManager = binding.recyclerViewGroup.layoutManager as LinearLayoutManager
val tabListener = object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val tabPosition = tab?.position
if (tabPosition != null) {
viewModel.setTabPosition(tabPosition)
// prevent RecyclerView to snap to its item start position while user scrolling,
// idk how to explain this XD
if (!isUserScrolling){
layoutManager.scrollToPositionWithOffset(tabPosition, 0)
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}
}
binding.tabLayout.addOnTabSelectedListener(tabListener)
// Detect recyclerview scroll state
val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isUserScrolling = true
} else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
isUserScrolling = false
}
}
// this just represent my tab name using enum class ,
// and ordinal is just the index of its position in enum
val hardcase3D = CaseType.HARDCASE_3D.ordinal
val softcaseBlackmatte = CaseType.SOFTCASE_BLACKMATTE.ordinal
val softcaseTransparent = CaseType.SOFTCASE_TRANSPARENT.ordinal
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (isUserScrolling) {
when (layoutManager.findFirstVisibleItemPosition()) {
in hardcase3D until softcaseBlackmatte -> {
viewModel.setTabPosition(hardcase3D)
}
in softcaseBlackmatte until softcaseTransparent -> {
viewModel.setTabPosition(softcaseBlackmatte)
}
softcaseTransparent -> {
viewModel.setTabPosition(softcaseTransparent)
}
}
}
}
}
binding.recyclerViewGroup.addOnScrollListener(onScrollListener)
}
viewModel , you can simply use liveData if you want
private var _tabPosition = MutableStateFlow(CaseType.HARDCASE_3D)
val tabPostition : StateFlow<CaseType>
get() = _tabPosition
fun setTabPosition(position: Int){
_tabPosition.value = CaseType.values()[position]
}
Observer
lifecycleScope.launch(Dispatchers.Default) {
viewModel.tabPostition.collect { caseType ->
val positionIndex = CaseType.values().indexOf(caseType)
handleSelectedTab(positionIndex)
}
}
and handleSelectedTab
private fun handleSelectedTab(index: Int) {
val tab = binding.tabLayout.getTabAt(index)
tab?.select()
}
enum
enum class CaseType(val caseTypeName:String) {
HARDCASE_3D("Hardcase 3D"),
SOFTCASE_BLACKMATTE("Softcase Blackmatte"),
SOFTCASE_TRANSPARENT("Softcase Transparent")
}