i am calling data from API to display it to use but in my case i want to display the latest 4 images of the API, i don't want to call all of them
so what is the correct way to do this?
Here is my code:
Interface:
interface facebookInterface {
#GET(" ")
fun getServices(): Call<List<facebookData>>
companion object Factory {
fun create(): facebookInterface {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("API_URL")
.build()
return retrofit.create(facebookInterface::class.java)
}
}
}
Adapter:
class facebookAdapter(var countryList: List<facebookData>, var activity: MainActivity): RecyclerView.Adapter<facebookAdapter.ViewHolder>(){
lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): facebookAdapter.ViewHolder {
context = parent.context!!
val view = LayoutInflater.from(context).inflate(R.layout.facebook_list_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return countryList.size
}
override fun onBindViewHolder(holder: facebookAdapter.ViewHolder, position: Int) {
holder.name.text = countryList[position].source
holder.capital.text = countryList[position].imageLink
Picasso.get()
.load(countryList[position].imageUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
.into(holder.thumbnailUrl)
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val name: TextView = itemView.findViewById(R.id.country_name)
val capital: TextView = itemView.findViewById(R.id.country_capital)
val thumbnailUrl: ImageView = itemView.findViewById(R.id.country_flag)
}
}
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:id="#+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="#+id/list_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="4dp"
app:contentPadding="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/list_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/country_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:text="TextView"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/country_capital"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:fontFamily="monospace"
android:text="TextView"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/country_name" />
<ImageView
android:id="#+id/country_flag"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
Activity:
val recyclerView: RecyclerView = findViewById(R.id.list_recycler4)
recyclerView.layoutManager = LinearLayoutManager(this)
val facebookInterface = facebookInterface.Factory.create()
val facebok: Call<List<facebookData>> = facebookInterface.getServices()
facebok.enqueue(object : Callback<List<facebookData>> {
override fun onFailure(call: Call<List<facebookData>>, t: Throwable) {
Log.d("xxxx", t.toString())
Toast.makeText(this#MainActivity, t.message, Toast.LENGTH_SHORT).show()
}
override fun onResponse(
call: Call<List<facebookData>>,
response: Response<List<facebookData>>
) {
Log.d("xxxx", response.body().toString())
val list = response.body()!!
recyclerView.adapter = facebookAdapter(list, this#MainActivity)
}
})
data class:
data class facebookData (
val imageUrl : String,
val imageLink: String,
val source: String
)
i am getting the data and i can see it on the device but is there a way to display only the latest 4 images from API?
and how to link the url of each image so the user can go to the url of each image
"English is not my mother tongue, i will explain more if you didn't understand me"
As Teo said, you are better of applying a filter directly to your API call, but if that is not possible, you can trim off the list you receive.
There are mainly two methods I can think of, that can get you what you want, first is to make a sublist and then assign the adapter as follows.
val trimmedList = if(list.size > 4){
list.subList(fromIndex = 0, toIndex = 3)
} else {
list
}
recyclerView.adapter = facebookAdapter(trimmedList , this#MainActivity)
Second way to override the itemCount method inside the Recycler View Adapter, same logic shall apply there. It will be as follows.
override fun getItemCount(): Int {
return if(countryList.size > 4) {
4
} else {
countryList.size
}
}
For second part of your answer, I am adding the following code for your reference, but I still believe it is better for you to understand what is happening.
To open the url in browser, firstly you need a callback from your Adapter to your MainActivity.
Your adapter will be modified as the following.
class facebookAdapter(var countryList: List<facebookData>,
private val facebookAdapterCallback: FacebookAdapterCallback)
: RecyclerView.Adapter<facebookAdapter.ViewHolder>(){
lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): facebookAdapter.ViewHolder {
context = parent.context!!
val view = LayoutInflater.from(context).inflate(R.layout.facebook_list_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return countryList.size
}
override fun onBindViewHolder(holder: facebookAdapter.ViewHolder, position: Int) {
holder.name.text = countryList[position].source
holder.capital.text = countryList[position].imageLink
holder.thumbnailUrl.setOnClickListener {
facebookAdapterCallback.onFacebookAdapterItemClick(countryList[position])
}
Picasso.get()
.load(countryList[position].imageUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
.into(holder.thumbnailUrl)
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val name: TextView = itemView.findViewById(R.id.country_name)
val capital: TextView = itemView.findViewById(R.id.country_capital)
val thumbnailUrl: ImageView = itemView.findViewById(R.id.country_flag)
}
interface FacebookAdapterCallback{
fun onFacebookAdapterItemClick(data: facebookData)
}
}
Next we will have to modify your activity as follows to implement the above interface inside it
class MainActivity : AppCompatActivity(), facebookAdapter.FacebookAdapterCallback {
override fun onFacebookAdapterItemClick(data: facebookData) {
openLinkInBrowser(this#MainActivity, data.imageLink)
}
fun openLinkInBrowser(context: Context, url: String) {
try {
val url = if (Uri.parse(url).scheme == null || !url.startsWith("https://") && !url.startsWith("http://")) {
"https://$url"
} else url
val webPage: Uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, webPage)
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
Toast.makeText(context, "No App Available for this action", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
I have also added a helper method to open the link in browser, it uses the default Android Intents and checks for apps available in which this action can be performed.
We also check if the URL has http or https appended else we add it.
While I have provided you the code, I would still recommend you to understand what is happening before doing it.
Related
I am working with recycler view, but it was behaving differently now.
The issue is when we scroll from top to bottom all things work okay, but when scrolling bottom to top it not working as expected.
I am loading arround 450 Items.
Please find more details in the attached video (Last Few Seconds Very Important).
Video : https://www.dropbox.com/s/rb7y52av6wwcq3q/RecyclerViewIssue.mp4?dl=0
Gradle dependencies
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
//RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.2.1'
//Other dependencies
XML Code
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rcvJobList"
android:layout_width="match_parent"
android:layout_height="#dimen/_400sdp"
android:background="#drawable/box2"
android:clipChildren="false"
android:paddingTop="#dimen/_5sdp"
android:paddingBottom="#dimen/_5sdp"
tools:itemCount="20"
tools:listitem="#layout/item_job" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activity Code
class HomeActivity : BaseActivity<HomeViewModel, ActivityHomeBinding, HomeRepository>(),
RcvJobListAdapter.AdapterListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Other Code
ViewModel.jobList.observe(this, jobListObserver)
}
// Live data observer code
val jobListObserver = Observer<List<Job>> {
Log.e("HomeActivity", "Job List Observer Invoked Size : ${it.size}");
var tempList = arrayListOf<Job>()
tempList.addAll(it)
var adapter = RcvJobListAdapter(tempList, this#HomeActivity)
binding.rcvJobList.adapter = adapter
}
}
Adapter Code
class RcvJobListAdapter : RecyclerView.Adapter<RcvJobListAdapter.ViewHolder> {
var jobList: List<Job> = emptyList()
private lateinit var callback: AdapterListener
constructor(
jobList: List<Job>,
callback: AdapterListener,
) : super() {
this.jobList = jobList
this.callback = callback
}
class ViewHolder(var itemView: View) : RecyclerView.ViewHolder(itemView)
{
var binding = ItemJobBinding.bind(itemView)
init {
binding.root.setBackgroundColor(Color.WHITE)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_job,parent,false)
)
}
var prevPosition : Int = -1
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.e("Adapter","Bind Pos : ${holder.bindingAdapterPosition}, Absolute : ${holder.absoluteAdapterPosition} , Last ${prevPosition}")
var job = jobList.get(holder.bindingAdapterPosition)
holder.binding.tvJobText.setText("${holder.bindingAdapterPosition}\n${job.getCombainedString()}")
holder.binding.tvJobText.setOnClickListener {
if(prevPosition!=-1)
{
var temp = prevPosition
prevPosition = -1;
notifyItemChanged(temp)
if(temp == holder.bindingAdapterPosition)
{
return#setOnClickListener
}
}
prevPosition = holder.bindingAdapterPosition
holder.binding.root.setBackgroundColor(Color.parseColor("#BCF1C9"))
}
if(prevPosition == holder.bindingAdapterPosition)
{
holder.binding.root.setBackgroundColor(Color.parseColor("#BCF1C9"))
}
else
{
holder.binding.root.setBackgroundColor(Color.WHITE)
}
}
override fun getItemCount(): Int = jobList.size
open interface AdapterListener {
fun jobItemClick(position: Int, job: Job);
}
}
I am using Agora for the video call function. I have run the sample code of agora for 1-1 video call referencing this - https://docs.agora.io/en/Video/start_call_android?platform=Android
In the sample we have one local view and one remote view, this is working well. Now I want to show two remote views and one local view. For that I created layout which has two remote views and one local view.
Also in onFirstRemoteVideoDecoded method I checked if the 1st remote view is added then setup 1st remote view else setup 2nd remoteview. Same for onUserOffline when the user is left I checked if 1st remote view is logged of the run method onRemoteUserLeft() or onRemoteUserLeft1() which removes the related views.
To join the channel I am providing same channel name and different tokens
`
var token1: String? = ("<token-string>")
if (token1!!.isEmpty()) {
token1 = null
}
mRtcEngine!!.joinChannel(
token1,
"67124678",
"null",
0
)
I have a link which provide the channel name and the related token. https://mobile.quirkysemicolon.com/getTokens.php
I am confused about how can I use this link in my app to join multiple user on same channel.
Can anyone suggest how can I achieve this functionality?
Here is my 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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/remote_video_view_container"
android:layout_width="0dp"
android:layout_height="284dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="#android:color/darker_gray"
app:layout_constraintEnd_toStartOf="#+id/remote1_video_view_container"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="#+id/remote1_video_view_container"
android:layout_width="0dp"
android:layout_height="284dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="#android:color/darker_gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/remote_video_view_container"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="#+id/local_video_view_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/container"
app:layout_constraintTop_toBottomOf="#+id/remote1_video_view_container" />
<RelativeLayout
android:id="#+id/container"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/white"
android:padding="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="#+id/local_video_view_container"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/remote1_video_view_container">
<TextView
android:id="#+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:textSize="18sp"
android:textColor="#android:color/black"
android:text="Channel :" />
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start"
android:textColor="#android:color/white"
android:background="#android:color/holo_green_dark"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:layout_below="#id/textView2"/>
<Button
android:id="#+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="End"
android:onClick="onEndCallClicked"
android:textColor="#android:color/white"
android:background="#android:color/holo_red_dark"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:layout_below="#id/button"/>
<TextView
android:id="#+id/textView5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/button2"
android:layout_marginTop="20dp"
android:textSize="18sp"
android:textColor="#android:color/black"
android:layout_gravity="center_horizontal"
android:text="Token(Push Notify) : " />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Ans here is my activity
class MultipleRemoteViewActivity: AppCompatActivity() {
private var mRtcEngine: RtcEngine? = null
private var mReceiver: BroadcastReceiver? = null
var mIntentFilter: IntentFilter? = null
var mIsRemote1Joined: Boolean = false
var mIsRemote1Left: Boolean = false
private val mRtcEventHandler = object : IRtcEngineEventHandler() {
/**
* Occurs when the first remote video frame is received and decoded.
* This callback is triggered in either of the following scenarios:
*
* The remote user joins the channel and sends the video stream.
* The remote user stops sending the video stream and re-sends it after 15 seconds. Possible reasons include:
* The remote user leaves channel.
* The remote user drops offline.
* The remote user calls the muteLocalVideoStream method.
* The remote user calls the disableVideo method.
*
* #param uid User ID of the remote user sending the video streams.
* #param width Width (pixels) of the video stream.
* #param height Height (pixels) of the video stream.
* #param elapsed Time elapsed (ms) from the local user calling the joinChannel method until this callback is triggered.
*/
override fun onFirstRemoteVideoDecoded(uid: Int, width: Int, height: Int, elapsed: Int) {
runOnUiThread {
if(!mIsRemote1Joined)
setupRemoteVideo(uid)
else
setupRemoteVideo1(uid)
}
}
override fun onUserOffline(uid: Int, reason: Int) {
runOnUiThread {
if(!mIsRemote1Left)
onRemoteUserLeft()
else
onRemoteUserLeft1()
}
}
override fun onUserMuteVideo(uid: Int, muted: Boolean) {
runOnUiThread { onRemoteUserVideoMuted(uid, muted) }
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.multiple_remoteview_layout)
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO,
MultipleRemoteViewActivity.PERMISSION_REQ_ID_RECORD_AUDIO
) && checkSelfPermission(
Manifest.permission.CAMERA, MultipleRemoteViewActivity.PERMISSION_REQ_ID_CAMERA
)) {
initAgoraEngineAndJoinChannel()
}
}
private fun initAgoraEngineAndJoinChannel() {
initializeAgoraEngine()
}
private fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
Log.i(MultipleRemoteViewActivity.LOG_TAG, "checkSelfPermission $permission $requestCode")
if (ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(permission),
requestCode
)
return false
}
return true
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>, grantResults: IntArray
) {
Log.i(MultipleRemoteViewActivity.LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode)
when (requestCode) {
MultipleRemoteViewActivity.PERMISSION_REQ_ID_RECORD_AUDIO -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
checkSelfPermission(Manifest.permission.CAMERA,
MultipleRemoteViewActivity.PERMISSION_REQ_ID_CAMERA
)
} else {
showLongToast("No permission for " + Manifest.permission.RECORD_AUDIO)
finish()
}
}
MultipleRemoteViewActivity.PERMISSION_REQ_ID_CAMERA -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initAgoraEngineAndJoinChannel()
} else {
showLongToast("No permission for " + Manifest.permission.CAMERA)
finish()
}
}
}
}
private fun showLongToast(msg: String) {
this.runOnUiThread { Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG).show() }
}
override fun onDestroy() {
super.onDestroy()
leaveChannel()
/*
Destroys the RtcEngine instance and releases all resources used by the Agora SDK.
This method is useful for apps that occasionally make voice or video calls,
to free up resources for other operations when not making calls.
*/
RtcEngine.destroy()
mRtcEngine = null
}
fun onLocalVideoMuteClicked(view: View) {
/* val iv = view as ImageView
if (iv.isSelected) {
iv.isSelected = false
iv.clearColorFilter()
} else {
iv.isSelected = true
iv.setColorFilter(resources.getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY)
}
// Stops/Resumes sending the local video stream.
mRtcEngine!!.muteLocalVideoStream(iv.isSelected)*/
val container = findViewById(R.id.local_video_view_container) as FrameLayout
val surfaceView = container.getChildAt(0) as SurfaceView
// surfaceView.setZOrderMediaOverlay(!iv.isSelected) // surfaceView.visibility = if (iv.isSelected) View.GONE else View.VISIBLE
}
/* fun onLocalAudioMuteClicked(view: View) {
val iv = view as ImageView
if (iv.isSelected) {
iv.isSelected = false
iv.clearColorFilter()
} else {
iv.isSelected = true
iv.setColorFilter(resources.getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY)
}
// Stops/Resumes sending the local audio stream.
mRtcEngine!!.muteLocalAudioStream(iv.isSelected)
}*/
/*
fun onSwitchCameraClicked(view: View) {
// Switches between front and rear cameras.
mRtcEngine!!.switchCamera()
}
*/
fun onEndCallClicked(view: View) {
finish()
}
private fun initializeAgoraEngine() {
try {
mRtcEngine = RtcEngine.create(
baseContext,
getString(R.string.agora_app_id),
mRtcEventHandler
)
setupVideoProfile()
setupLocalVideo()
joinChannel()
} catch (e: Exception) {
Log.e(MultipleRemoteViewActivity.LOG_TAG, Log.getStackTraceString(e))
throw RuntimeException(
"NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e)
)
}
}
private fun setupVideoProfile() {
// In simple use cases, we only need to enable video capturing
// and rendering once at the initialization step.
// Note: audio recording and playing is enabled by default.
mRtcEngine!!.enableVideo()
// mRtcEngine!!.setVideoProfile(Constants.VIDEO_PROFILE_360P, false) // Earlier than 2.3.0
// Please go to this page for detailed explanation
// https://docs.agora.io/en/Video/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#af5f4de754e2c1f493096641c5c5c1d8f
mRtcEngine!!.setVideoEncoderConfiguration(
VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x360,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
)
)
}
private fun setupLocalVideo() {
val container = findViewById(R.id.local_video_view_container) as FrameLayout
val surfaceView = RtcEngine.CreateRendererView(baseContext)
surfaceView.setZOrderMediaOverlay(true)
container.addView(surfaceView)
// Initializes the local video view.
// RENDER_MODE_FIT: Uniformly scale the video until one of its dimension fits the boundary. Areas that are not filled due to the disparity in the aspect ratio are filled with black.
mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
}
private fun joinChannel() {
// 1. Users can only see each other after they join the
// same channel successfully using the same app id.
// 2. One token is only valid for the channel name that
// you use to generate this token.
var token: String? = getString(R.string.agora_access_token)
if (token!!.isEmpty()) {
token = null
}
mRtcEngine!!.joinChannel(
token,
"67124678",
"null",
0
) // if you do not specify the uid, we will generate the uid for you
var token1: String? = ("<token-string>")
if (token1!!.isEmpty()) {
token1 = null
}
mRtcEngine!!.joinChannel(
token1,
"67124678",
"null",
0
) // if you do not specify the uid, we will generate the uid for you
}
private fun setupRemoteVideo(uid: Int) {
// Only one remote video view is available for this
// tutorial. Here we check if there exists a surface
// view tagged as this uid.
val container = findViewById(R.id.remote_video_view_container) as FrameLayout
if (container.childCount >= 1) {
return
}
/*
Creates the video renderer view.
CreateRendererView returns the SurfaceView type. The operation and layout of the view
are managed by the app, and the Agora SDK renders the view provided by the app.
The video display view must be created using this method instead of directly
calling SurfaceView.
*/
val surfaceView = RtcEngine.CreateRendererView(baseContext)
container.addView(surfaceView)
// Initializes the video view of a remote user.
mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))
surfaceView.tag = uid // for mark purpose
mIsRemote1Joined = true
// val tipMsg = findViewById<TextView>(R.id.quick_tips_when_use_agora_sdk) // optional UI
// tipMsg.visibility = View.GONE
}
private fun setupRemoteVideo1(uid: Int) {
// Only one remote video view is available for this
// tutorial. Here we check if there exists a surface
// view tagged as this uid.
val container1 = findViewById(R.id.remote1_video_view_container) as FrameLayout
if (container1.childCount >= 1) {
return
}
/*
Creates the video renderer view.
CreateRendererView returns the SurfaceView type. The operation and layout of the view
are managed by the app, and the Agora SDK renders the view provided by the app.
The video display view must be created using this method instead of directly
calling SurfaceView.
*/
val surfaceView1 = RtcEngine.CreateRendererView(baseContext)
container1.addView(surfaceView1)
// Initializes the video view of a remote user.
mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView1, VideoCanvas.RENDER_MODE_FIT, uid))
surfaceView1.tag = uid // for mark purpose
// val tipMsg = findViewById<TextView>(R.id.quick_tips_when_use_agora_sdk) // optional UI
// tipMsg.visibility = View.GONE
}
private fun leaveChannel() {
mRtcEngine!!.leaveChannel()
}
private fun onRemoteUserLeft() {
val container = findViewById(R.id.remote_video_view_container) as FrameLayout
container.removeAllViews()
mIsRemote1Left = true
}
private fun onRemoteUserLeft1() {
val container1 = findViewById(R.id.remote1_video_view_container) as FrameLayout
container1.removeAllViews()
}
private fun onRemoteUserVideoMuted(uid: Int, muted: Boolean) {
val container = findViewById(R.id.remote_video_view_container) as FrameLayout
val surfaceView = container.getChildAt(0) as? SurfaceView
val tag = surfaceView?.tag
if (tag != null && tag as Int == uid) {
surfaceView.visibility = if (muted) View.GONE else View.VISIBLE
}
val container1 = findViewById(R.id.remote1_video_view_container) as FrameLayout
val surfaceView1 = container1.getChildAt(0) as? SurfaceView
val tag1 = surfaceView1?.tag
if (tag1 != null && tag1 as Int == uid) {
surfaceView1.visibility = if (muted) View.GONE else View.VISIBLE
}
}
companion object {
private val LOG_TAG = MultipleRemoteViewActivity::class.java.simpleName
private const val PERMISSION_REQ_ID_RECORD_AUDIO = 22
private const val PERMISSION_REQ_ID_CAMERA = PERMISSION_REQ_ID_RECORD_AUDIO + 1
}
}
Now issue inmy code is: I am joining same channel with same app id on three devices one is web- chrome app and other two on android devices. When I join from two devices I am able to see one remote view and one local view but when the three devices have joined the same channel and if one of the device leaves the channel both the remote views stop working and the unable to join the same again..
Please suggest the solution...
You can take a look at our group video sample app for your reference: https://github.com/AgoraIO/Basic-Video-Call/tree/master/Group-Video/OpenVideoCall-Android
In General, if you want to make multiple people join the same channel, you will need to make sure they have the same app id and channel name.
Regarding token, each user who will join the channel needs to have separate UID which also means that their token will be different.
So the workflow will look like the following:
User A wants to join the channel -> User A sends request to the backend -> Server generate a random UID and generates a new token using that -> Server returns the generated UID and token -> User A uses those credentials to join the channel
Same workflow will be used for every subsequent user that joins the channel.
Keep in mind that for this workflow, the UID is being generated on your side and not randomly generated by Agora (i.e you shouldn't pass 0 as the UID like you have done).
To understand it better read this :
First game :
Skate QuestionMark
Archery QuestionMark
Swim QuestionMark
---------------------------
Water Bow Wheel
If user drags Water to Skate or Archery QuestionMark it will animate to the list (because it is not correct)
If user drags twice incorrect (it will mark the one that is correct from the answer list)
If user still fail in the third try it will drag to the incorrect one and then it will highlight or just change the color doesn't matter to red.
If user drags to the correct one it will highlight green and replace QuestionMark with the correct one (I do not want to be draggable anymore that image)
------
Game 2 (is more or less the same...)
There's no QuestionMark column, there's only :
Skate
Swim
Archery
--------------
(Lot of answers)
Now the way to play is the same (about the fails and etc) the thing is now when I drag any answer to the correct one it won't replace the correct one, it will just disappear and if it fails instead of highlighting all the corrects one it will highlight the correct answer (for instance; if I drag wheel to Swim once, it doesn't happen anything just animate to the place where it was, if I do it twice it will highlight the Skate one, and if it fails at third one it just drag wherever he did and highlight with red)
I'm planning to build an app that does a simple check, I'm calling an endpoint and I'll get some params, and then I'll know how many ImageView are going to be displayed in the screen.
This is like a puzzle, and it would look like this :
So I have different options, which contains only one correct answer, I'm planing the way to achieve this, could be able to drag "Bow" to the questionmark infront of "skateboarding" and then says that is not correct, then drag it to the "archery" one and replace the questionmark for the ImageView from the bottom that contains the word "Arrow".
Layout should contain one column for Question (this should be the sports) then another one in front of the Question one and should be the Answer one, then below them should contain the Options one.
Was it clear? Otherwise let me know and I'll try to explain it a little bit with more details.
EDIT
What I thought is having like a class that contains a list of Answers or just create like :
RightList : (id:1,id:2,id:3)
LeftList : (id:1, id:2, id:3)
DownList : (Bow = id:2),(Skate = id:1), (Ball = id:3)
Then doing the drag and drop thing when the DragEvent.ACTION_DROP or DragEvent.ACTION_DRAG_ENDEDI do not know which one, check (Pseudocode below)
if(imageDragged.id==location.id) then replace the question mark image for imageDragged
else animate the image to the place where it comes
I do not know if creating a class that implements onDragListener() or something like that, I'd like to have it generic so I can use it on different games like for instance :
SKATE(id:1) ARCHERY(id:2) FOOTBALL(id:3)
Answers : TABLE(C.A. id:1) BOW(C.A. id:2) GRASS(C.A. id:3) GOAL(C.A. id:3) BALL(C.A. id:3) ARROW(C.A. id:2) AXES(C.A. id:1) WHEELS(C.A. id:1)
So if I drag and drop for instance BOW to FOOTBALL then it should display that is bad, otherwise say that it's good.
EXAMPLE 1/3
Just for reference and summarize everything. Here is one 100 lines code, within single Activity and imports, representing all this behavior even with simple animation.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_question, null)
view.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_answer, null)
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
dragMultiple(view)
true
} else {
false
}
}
private fun dragMultiple(view : View) {
val data = ClipData.newPlainText("", "")
val shadowBuilder = DragShadowBuilder(
view
)
val parent = view.parent as ViewGroup
view.startDragAndDrop(data, shadowBuilder, view, 0)
parent.removeView(view)
}
}
private inner class DragListener : OnDragListener {
override fun onDrag(v: View, event: DragEvent): Boolean {
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
animateDropEffect(v as ViewGroup, event.localState as View)
}
DragEvent.ACTION_DRAG_ENDED -> {
}
else -> {
}
}
return true
}
private fun animateDropEffect(into: ViewGroup, view: View) {
into.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
}
}
}
All Xmls used. Below xml for all examples below.
/* activity_main.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/mainContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="500dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/questionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/answerContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
/* item_question.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:padding="5dp">
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="start"
android:background="#android:color/holo_blue_bright">
</View>
<View
android:id="#+id/questionView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="end"
android:background="#android:color/holo_orange_light">
</View>
</FrameLayout>
/* item_answer.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp"
android:tag="Test">
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#android:color/darker_gray">
</LinearLayout>
</FrameLayout>
EXAMPLE 2/3
It's not a problem to make dragging for few elements following with a the same approach. Here is a little crappy, but simple example.
Modified code for second example. Xml stay the same.
class MainActivity : AppCompatActivity() {
var activeOneDrag : Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
fun getRandomColor(): Int {
return Color.argb(255, Random.nextInt(255),
Random.nextInt(255), Random.nextInt(255))
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_question, null)
view.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_answer, null)
(view as ViewGroup).getChildAt(0).setBackgroundColor(getRandomColor())
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
dragMultiple(view)
true
} else {
false
}
}
private fun dragMultiple(view : View) {
val parent = view.parent as ViewGroup
parent.removeView(view)
/**
* Some other logic with selective multiple View.
* Just getting neighbor in our case
*/
var anotherView : View? = null
if (!activeOneDrag) {
anotherView = parent.getChildAt(
parent.indexOfChild(view) + 1)
parent.removeView(anotherView)
}
activeOneDrag = !activeOneDrag
/**
* As you can see, there is postDelay here.
* But only for our case with animateLayoutChanges,
* with delays removing View! In your samples, you could remove it
* with listener on your own animation, if any!
*/
parent.postDelayed({
val layout = LinearLayout(this#MainActivity)
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
params.gravity = Gravity.BOTTOM
layout.layoutParams = params
layout.orientation = LinearLayout.HORIZONTAL
layout.addView(view)
if (anotherView != null) {
layout.addView(anotherView)
}
layout.visibility = INVISIBLE
mainContainer.addView(layout)
parent.post {
layout.startDragAndDrop(
ClipData.newPlainText("", ""),
DragShadowBuilder(layout), layout, 0)
}
}, 400)
}
}
private inner class DragListener : OnDragListener {
override fun onDrag(v: View, event: DragEvent): Boolean {
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
val view = event.localState as View
(view.parent as ViewGroup).removeView(view)
view.visibility = VISIBLE
animateDropEffect(v as ViewGroup, event.localState as View)
}
DragEvent.ACTION_DRAG_ENDED -> {
}
else -> {
}
}
return true
}
private fun animateDropEffect(into: ViewGroup, view: View) {
into.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
}
}
}
EXAMPLE 3/3
As I see, it's not clear, how to change simple actions with animation or Drag listening area. Here is another simple example of doing all actions
class MainActivity : AppCompatActivity() {
#Volatile
var state : State = State.INACTIVE
enum class State {
ACTIVE, INACTIVE, HANDLED
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
private fun getRandomColor(): Int {
return Color.argb(255, Random.nextInt(255),
Random.nextInt(255), Random.nextInt(255))
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_question, null)
view.findViewById<View>(R.id.questionView)
.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..8) {
val view = inflater.inflate(R.layout.item_answer, null)
(view as ViewGroup).getChildAt(0).setBackgroundColor(getRandomColor())
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
val ITEM_INDEX_D = "Index-From"
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
createDrag(view)
true
} else {
false
}
}
private fun createDrag(view : View) {
val parent = view.parent as ViewGroup
view.tag = Pair(ITEM_INDEX_D,
parent.indexOfChild(view))
view.startDragAndDrop(ClipData.newPlainText("", ""),
DragShadowBuilder(view), view, 0)
parent.removeView(view)
parent.setBackgroundColor(Color.WHITE)
}
}
private inner class DragListener : OnDragListener {
override fun onDrag(parent: View, event: DragEvent): Boolean {
val view = event.localState as View
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
state = State.ACTIVE
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
state = State.HANDLED
animateDropEffect(parent, view)
return true
}
DragEvent.ACTION_DRAG_ENDED -> {
if (state == State.ACTIVE) {
state = State.INACTIVE
animateMoveBack(view,
(view.tag as Pair<*, *>).second as Int)
}
return true
}
else -> {
}
}
return true
}
private fun animateMoveBack(view: View, index : Int) {
answerContainer.addView(view, index)
}
private fun animateDropEffect(into: View, view: View) {
val parent = (into.parent as ViewGroup)
parent.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
checkIsCorrect(parent)
}
private fun checkIsCorrect(parent : ViewGroup) {
val correct = Random.nextBoolean()
val colorFrom = Color.WHITE
val colorTo : Int = if (correct) 0x8000ff00.toInt() else 0x80ff0000.toInt()
ObjectAnimator.ofObject(
parent,
"backgroundColor",
ArgbEvaluator(),
colorFrom,
colorTo
)
.setDuration(1000)
.start()
}
}
}
UPDATE
The last update from the comments sections. I think it's enough, and of course you would need you changes. So just change two "if" statement to align with your requirements and animation.
class MainActivity : AppCompatActivity() {
enum class State {
ACTIVE, INACTIVE, HANDLED
}
var state : State = State.INACTIVE
var failsCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bind()
}
private fun bind() {
addQuestions()
addAnswers()
}
private fun getRandomColor(): Int {
return Color.argb(255, Random.nextInt(255),
Random.nextInt(255), Random.nextInt(255))
}
#SuppressLint("InflateParams")
private fun addQuestions() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..3) {
val view = inflater.inflate(R.layout.item_question, null)
view.findViewById<View>(R.id.questionView)
.setOnDragListener(DragListener())
questionContainer.addView(view)
}
}
#SuppressLint("InflateParams")
private fun addAnswers() {
val inflater = getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
for (i in 1..3) {
val view = inflater.inflate(R.layout.item_answer, null)
(view as ViewGroup).getChildAt(0).setBackgroundColor(getRandomColor())
view.setOnTouchListener(DragItemTouchListener())
answerContainer.addView(view)
}
}
private inner class DragItemTouchListener : OnTouchListener {
val ITEM_INDEX_D = "Index-From"
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
createDrag(view)
true
} else {
false
}
}
private fun createDrag(view : View) {
val parent = view.parent as ViewGroup
view.tag = Pair(ITEM_INDEX_D,
parent.indexOfChild(view))
view.startDragAndDrop(ClipData.newPlainText("", ""),
DragShadowBuilder(view), view, 0)
parent.removeView(view)
parent.setBackgroundColor(Color.WHITE)
}
}
private inner class DragListener : OnDragListener {
val ANIM_DURATION_LONG = TimeUnit.SECONDS.toMillis(1)
val ANIM_DURATION_SHORT = TimeUnit.MILLISECONDS.toMillis(500)
val GREEN_ALPHA = 0x8000ff00.toInt()
val RED_ALPHA = 0x80ff0000.toInt()
val ANIM_COLOR = "backgroundColor"
override fun onDrag(parent: View, event: DragEvent): Boolean {
val view = event.localState as View
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> {
state = State.ACTIVE
}
DragEvent.ACTION_DRAG_ENTERED -> {
}
DragEvent.ACTION_DRAG_EXITED -> {
}
DragEvent.ACTION_DROP -> {
state = State.HANDLED
animateDropEffect(parent, view)
return true
}
DragEvent.ACTION_DRAG_ENDED -> {
if (state == State.ACTIVE) {
state = State.INACTIVE
animateMoveBack(view,
(view.tag as Pair<*, *>).second as Int)
}
return true
}
else -> {
}
}
return true
}
private fun animateMoveBack(view: View, index : Int) {
answerContainer.addView(view, index)
}
private fun animateDropEffect(into: View, view: View) {
val parent = (into.parent as ViewGroup)
parent.addView(view)
val params = (view.layoutParams as FrameLayout.LayoutParams)
.apply {
gravity = Gravity.END
}
view.layoutParams = params
checkIsCorrect(parent)
}
private fun checkIsCorrect(parent : ViewGroup) {
val correct = false
if (correct) {
animateColorChange(parent, true)
return
}
if (++failsCount > Companion.MAX_FAIL_COUNT) {
animateColorChange(parent, false)
return
}
animateWrongAttempt(parent)
}
private fun animateWrongAttempt(parent: ViewGroup) {
val questionMark = parent.findViewById<View>(R.id.questionView)
questionMark.setBackgroundColor(Color.RED)
val va = ValueAnimator.ofFloat(1f, 1.1f)
va.interpolator = BounceInterpolator()
va.duration = ANIM_DURATION_SHORT
va.addUpdateListener { animation ->
questionMark.scaleX = animation.animatedValue as Float
questionMark.scaleY = animation.animatedValue as Float
}
va.start()
}
private fun animateColorChange(parent : ViewGroup, right : Boolean) {
val colorFrom = Color.WHITE
ObjectAnimator
.ofObject(parent, ANIM_COLOR,
ArgbEvaluator(), colorFrom,
if (right) GREEN_ALPHA else RED_ALPHA)
.setDuration(ANIM_DURATION_LONG)
.start()
}
}
companion object {
const val MAX_FAIL_COUNT = 2
}
}
And new xml.
/* activity_main.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/mainContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="500dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/questionContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<LinearLayout
android:id="#+id/answerContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
/* item_question.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:paddingTop="10dp">
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="start"
android:layout_margin="5dp"
android:background="#android:color/holo_blue_bright">
</View>
<View
android:id="#+id/questionView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="end"
android:layout_margin="5dp"
android:background="#android:color/holo_orange_light">
</View>
</FrameLayout>
/* item_answer.xml */
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp"
android:tag="Test">
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#android:color/darker_gray">
</LinearLayout>
</FrameLayout>
I think, there are various ways to do it. For me, seems nice, to split the screen into two main sections. a) To have single vertical list with Items and connections to "Question mark", refer to the image below.
Single row in a vertical list
So it's easy to change size to the item list, which you are retrieving from Server, by dynamically adding and removing simple View. Once correct tile would be places on the top of "Question mark" you will change new View on the top of this View. b) To create bottom list with possible "Answers" there will be Horizontal list, with this items. As you mentioned in our question.
Bottom horizontal list
Description
a) I really do't like implementation of such kind of task with Android widgets like RecyclerView. With small list of items, we could bring a lot of customization. For the first list I would use VerticalScrollView with LinearLayout and View (with any your layout).
Note. Your last layout could be ConstraintLayout. Ex. 1. In this case your dragged items would stick on exact places, where you live it. And In case it's stick on specific row with more than half size, you could change view for it (to green or red). Ex 2. Maybe even better way would be animate moving from place where live the tiles, to the nearest "Question mark" with "Property Animation"
b) To create bottom list I would use the same structure with `HorizontalListView". This will help to use "Property Animation" and not just animate dragging, but also move an object with your grading trajectory. Using dynamically added items in the containers with simple views will reflect on changes in bottom, or top lists.
Implementation
1) Initial listeners for each your "Answer" tiles.
// Assign the touch listener to your view which you want to move
findViewById(R.id.myimage1).setOnTouchListener(new MyTouchListener());
// This defines your touch listener
private final class MyTouchListener implements OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
view);
view.startDrag(data, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
} else {
return false;
}
}
}
2) Define each of your target Views in the top list.
findViewById(R.id.bottomright).setOnDragListener(new MyDragListener());
class MyDragListener implements OnDragListener {
Drawable enterShape = getResources().getDrawable(
R.drawable.shape_droptarget);
Drawable normalShape = getResources().getDrawable(R.drawable.shape);
#Override
public boolean onDrag(View v, DragEvent event) {
int action = event.getAction();
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
// do nothing
break;
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackgroundDrawable(enterShape);
break;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackgroundDrawable(normalShape);
break;
case DragEvent.ACTION_DROP:
// Dropped, reassign View to ViewGroup
View view = (View) event.getLocalState();
ViewGroup owner = (ViewGroup) view.getParent();
owner.removeView(view);
LinearLayout container = (LinearLayout) v;
container.addView(view);
view.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
v.setBackgroundDrawable(normalShape);
default:
break;
}
return true;
}
}
3) That is it you need to create dragging. Just to make another setup in the Activity and View changes.
However you could check few other examples of drag and drop implementation. But since you were using simple Views and Scrollable containers, you can align every sample to work with your lists.
I am developing an android project with Kotlin
I would like to get values position out of spinner listener.
I try to put a global variable and change it, but it is only changed in the listener but returns the initial value of x
var x:Int?=null
class sem1gpaactivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sem1gpa)
val firstterm01 = arrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
val spinner = findViewById<View>(R.id.spinnerfirsttermh1) as Spinner
val spinnerArrayAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, firstterm01)
// The drop down view
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
spinner.spinnerfirsttermh1!!.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
x = p2
}
override fun onNothingSelected(p0: AdapterView<*>?) {}
}
gpa1.text= x.toString()
}}
It is very easy, and you are making your thing complex. If you want the selected item position from the Spinner. Do it like this.
This is the XML of your Activity.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Spinner
android:layout_width="395dp"
android:layout_height="wrap_content" android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:id="#+id/spinner"/>
</RelativeLayout>
And then in your Code.
class MainActivity : AppCompatActivity() {
var x: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val spinnerItems = arrayOf("Item1", "Item2", "Item3", "Item4", "Item5", "Item6")
spinner.adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, spinnerItems)
spinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener{
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
x = position
Toast.makeText(applicationContext, position.toString(), Toast.LENGTH_SHORT).show()
}
}
}
}
The position parameter will tell you about the position. And you do not need to use findViewById() when working with Kotlin. So the code is simple and concise.
The position starts from 0 so you may need to add 1 in the position variable (depending upon your need).
Hope this helps.
I try to put a global variable and change it, but it is only changed in the listener but returns the initial value of x
Put var x:Int?=null in the line directly after class sem1gpaactivity : AppCompatActivity(){ and it should work. Otherwise your variable is not a global variable in the class.
You were very close on the right track.
I am working on an android app using Kotlin language and i need to save the fragment state when i click on another tab and then back to the same tab.
To make sure it is working as expected, i have inserted a counter that counts with each button click as shown in the below code but it seems there is an issue in my code as it resets to 0 each time i change the tab (i.e. not saving the state).
I went through many stackoverflow questions and many videos with no success.
Am i doing something wrong in my code below?
Appreciate your kind support please and thank you in advance.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var containerFrame: FrameLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
containerFrame = findViewById<FrameLayout>(R.id.containerFrame)
val navigation = findViewById<BottomNavigationView>(R.id.bottomBar)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectListener)
val fragment = HomeTabFragment() //.newInstance()
addFragment(fragment)
}
private val mOnNavigationItemSelectListener = object : BottomNavigationView.OnNavigationItemSelectedListener {
override fun onNavigationItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.TabHome -> {
val fragment = HomeTabFragment() //.newInstance()
addFragment(fragment)
return true
}
R.id.TabTwo -> {
val fragment = TabTwoFragment()
addFragment(fragment)
return true
}
R.id.TabThree -> {
val fragment = TabThreeFragment()
addFragment(fragment)
return true
}
R.id.TabFour -> {
val fragment = TabFourFragment()
addFragment(fragment)
return true
}
}
return false
}
}
private fun addFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.design_bottom_sheet_slide_in, R.anim.design_bottom_sheet_slide_out)
.replace(R.id.containerFrame, fragment, fragment.javaClass.simpleName)
.addToBackStack(fragment.javaClass.simpleName).commit()
}
}
HomeTabFragment.kt
class HomeTabFragment : Fragment() {
var counterOne: Int? = 0
companion object {
fun newInstance(): HomeTabFragment {
val homeTabFragment = HomeTabFragment()
val args = Bundle()
homeTabFragment.arguments = args
return homeTabFragment
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
counterOne = savedInstanceState!!.getInt("counter", 0)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater!!.inflate(R.layout.fragment_tab_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val testButtonOne = view!!.findViewById<Button>(R.id.testButtonOne)
val resultOne = view!!.findViewById<TextView>(R.id.resultOne)
resultOne.text = "$counterOne"
testButtonOne.setOnClickListener {
counterOne = counterOne!! + 1
resultOne.text = "$counterOne"
}
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("counter", counterOne!!)
}
}
activity_main.xml
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/containerFrame"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottomBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:menu="#menu/tabs" />
</LinearLayout>