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.
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_ENDED
I 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.
Drag event listeners and callback methods A View receives drag events with either a drag event listener that implements View. OnDragListener or with the view's onDragEvent() callback method.
Started − This event occurs when you start dragging an item in a layout, your application calls startDrag() method to tell the system to start a drag. The arguments inside startDrag() method provide the data to be dragged, metadata for this data, and a callback for drawing the drag shadow.
For adding an image from Android Studio, Drag the ImageView widget to the activity area of the application, a pop-up dialogue box will open choose from the wide range of drawable resources and click “OK“.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With