I'm trying to play a sequence of more than 1 animations on the same progress bar. I am actually using a library called AnimateHorizontalProgressBar which allows the progress bars to fill up with an animation. The bar is an experience bar in a game, and if the player gets enough experience at one time, I want the bar to fill up as many times as there are level ups. (e.g. a level 1 character going to level 3 would have their experience bar fill up 2 full times, resetting whenever it gets full, then once more for however much progress they made from level 3 to level 4)
The solution I tried was to set an AnimateProgressListener on the AnimateHorizontalProgressBar, and in the onAnimationEnd function, to call the next animation to be played. The list containing these animations is an arraylist of custom POJOs which hold a reference to the view (the progress bar), and the percent to set it to.
public class MonsterBattleResolutionFragment extends MMOBaseFragment {
private static final String TAG = "Resolution Frag";
boolean isVictorious;
boolean playerCapturedMonster;
private String wildMonsterId;
ToolTipManager tooltips;
int monsterLevel;
private FirebaseFunctions mFunctions;
private ArrayList<String> monsterKeys;
private ArrayList<PlayerMonster> monstersList;
private BattleResolutionXpGainedAdapter battleResolutionXpGainedAdapter;
private Typeface typeface;
TextView victoryTextView;
private ListView xpGainedLayoutContainer;
private RecyclerView itemRecyclerView;
private PlayerItemAdapter playerItemAdapter;
ArrayList<PlayerItem> playerItems;
private FirebaseUser user;
private DatabaseReference mDatabase;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_monster_battle_resolution, container, false);
mFunctions = FirebaseFunctions.getInstance();
typeface = CustomTypeFace.getNovemberTypeface(getContext());
victoryTextView = view.findViewById(R.id.monster_battle_resolution_text_view);
victoryTextView.setTypeface(typeface);
tooltips = new ToolTipManager(getActivity());
itemRecyclerView = view.findViewById(R.id.monster_battle_resolution_item_recycler_view);
itemRecyclerView.setNestedScrollingEnabled(false);
itemRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
playerItems = new ArrayList<>();
playerItemAdapter = new PlayerItemAdapter(playerItems);
itemRecyclerView.setAdapter(playerItemAdapter);
isVictorious = getArguments().getBoolean("victory");
playerCapturedMonster = getArguments().getBoolean("playerCapturedMonster");
monsterLevel = getArguments().getInt("monsterLevel");
monsterKeys = getArguments().getStringArrayList("monsterKeys");
monstersList = getArguments().getParcelableArrayList("monstersList");
for (int i = 0; i < monstersList.size(); i++){
Log.e(TAG, monstersList.get(i).getMonsterStringName());
}
wildMonsterId = getArguments().getString("wildMonsterId");
ArrayList<String> playerMonsterIds = new ArrayList<>();
for (int i = 0; i < monstersList.size(); i++){
Log.e(TAG, "fb id is " + monstersList.get(i).getFirebaseId());
playerMonsterIds.add(monstersList.get(i).getFirebaseId());
}
Task<HashMap<String, Integer>> test = firebaseResolveCombat(wildMonsterId, playerMonsterIds);
test.addOnCompleteListener(new OnCompleteListener<HashMap<String, Integer>>() {
#Override
public void onComplete(#NonNull Task<HashMap<String, Integer>> task) {
Object gainedXp = task.getResult().get("experience");
Object items = task.getResult().get("items");
Object skills = task.getResult().get("skills");
Object newMonsterLevels = task.getResult().get("newMonsterLevels");
// gainedXp is the xp gained for each monster involved in the fight
if (gainedXp != null){
Log.e(TAG, "experience earned: " + gainedXp);
updateXpBars((int) gainedXp);
}
if (items != null){
Log.e(TAG, "items earned: " + items.toString());
HashMap<String, Integer> itemsEarnedMap = (HashMap<String, Integer>) items;
for (Map.Entry<String, Integer> item : itemsEarnedMap.entrySet()) {
Log.e(TAG, "key: " + item.getKey() + ", value: " + item.getValue());
// add new item to list for adapter. NOTE: as of now, key is a string, while value (quantity) is an int. slight issue on javascript side, fine for now.
playerItems.add(ItemDirectory.itemLookup(Integer.parseInt(item.getKey()), item.getValue()));
}
}
if (skills != null){
Log.e(TAG, "new skills: " + skills.toString());
HashMap<String, ArrayList<Integer>> newSkills = (HashMap<String, ArrayList<Integer>>) skills;
for (Map.Entry<String, ArrayList<Integer>> item : newSkills.entrySet()) {
for (Integer skill : item.getValue()) {
String skillName = SkillDirectory.skillLookup(skill).getName();
Log.e(TAG, item.getKey() + " learned skill " + skillName);
for (int i = 0; i < monstersList.size(); i++){
Log.e(TAG, "fb id is " + monstersList.get(i).getFirebaseId());
if (monstersList.get(i).getFirebaseId().equals(item.getKey())){
Toast.makeText(getContext(), monstersList.get(i).getMonsterStringName() + " learned skill " + skillName + "!", Toast.LENGTH_SHORT).show();
}
}
}
}
}
if (newMonsterLevels != null){
Log.e(TAG, "new levels: " + newMonsterLevels.toString());
}
}
});
xpGainedLayoutContainer = view.findViewById(R.id.monster_battle_resolution_xp_gained_layout_container);
battleResolutionXpGainedAdapter = new BattleResolutionXpGainedAdapter(getActivity(), R.layout.battle_resolution_xp_gained_layout, monstersList);
xpGainedLayoutContainer.setAdapter(battleResolutionXpGainedAdapter);
justifyListViewHeightBasedOnChildren(xpGainedLayoutContainer);
// if the player did not win, don't show items won, give them half xp
if (!isVictorious) {
victoryTextView.setText(R.string.defeat);
}
// listener for button to return to map
FloatingActionButton b = view.findViewById(R.id.monster_battle_resolution_return_to_map_button);
b.setOnClickListener(view1 -> {
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
});
return view;
}
private void updateXpBars(int xpPerMonster){
ArrayList<LevelUpBarAnimation> animationList = new ArrayList<>();
// First build the list of animations
Log.e(TAG, "There are " + battleResolutionXpGainedAdapter.getCount() + " xp bars to animate");
for (int i = 0; i < battleResolutionXpGainedAdapter.getCount(); i++){
int currentXp = monstersList.get(i).getExperience();
int level = monstersList.get(i).getLevel();
int newXp = currentXp + xpPerMonster;
int lastLevelXp = getExperienceRequiredForNextLevel(level-1);
boolean isLevelUp = false;
int xpNumerator = newXp - lastLevelXp;
int xpDenominator = getExperienceRequiredForNextLevel(level) - lastLevelXp;
Log.e(TAG, "Monster was level: " + level + ", and had " + currentXp + " xp");
Log.e(TAG, "now has " + newXp + " xp. level now: " + getLevelBasedOnExperience(newXp));
// In a loop, build up every animation which needs to be done on this particular xp bar. Loop is so multiple animations may be played for multiple level ups\
// do while because even when the monster does not level up, they still will get some xp so at least 1 anim needs to be played
do {
isLevelUp = false; // Need to reset to know when we have not leveled up
AnimateHorizontalProgressBar bar = xpGainedLayoutContainer.getChildAt(i).findViewById(R.id.battle_resolution_xp_gained_xp_bar);
bar.setMax(10000);
if (newXp > getExperienceRequiredForNextLevel(level)){
isLevelUp = true;
level++;
xpNumerator = xpDenominator; // Sets the xp bar to full before resetting at 0 for the next level
}
Log.e(TAG, "Added new anim for bar at position " + i + ", " + "out of 10000: " + (int) (((double) xpNumerator / (double) xpDenominator) * 10000.0));
// TODO: clean up messy cast
LevelUpBarAnimation levelUpBarAnimation = new LevelUpBarAnimation(bar, (int) (((double) xpNumerator / (double) xpDenominator) * 10000.0), level);
levelUpBarAnimation.setLevelUp(isLevelUp);
Log.e(TAG, "islevelup set to " + String.valueOf(levelUpBarAnimation.isLevelUp()) + ", going from level " + (level-1) + " to " + (level));
levelUpBarAnimation.setLevelTxtView(xpGainedLayoutContainer.getChildAt(i).findViewById(R.id.battle_resolution_xp_gained_level_text_view));
animationList.add(levelUpBarAnimation);
} while(isLevelUp);
}
final int[] animNumber = {0};
Log.e(TAG, "animlist size " + animationList.size() + " playing across " + battleResolutionXpGainedAdapter.getCount() + " xp bars");
for (int i = 0; i < battleResolutionXpGainedAdapter.getCount(); i++) {
AnimateHorizontalProgressBar bar = animationList.get(i).getXpBar();
bar.setAnimateProgressListener(new AnimateHorizontalProgressBar.AnimateProgressListener() {
#Override
public void onAnimationStart(int progress, int max) {
Log.e(TAG, "anim #" + animNumber[0] + " starting");
}
#Override
public void onAnimationEnd(int progress, int max) {
// If this xp bar filled up, play a flash sort of animation, and update the lvl text
if (animationList.get(animNumber[0]).isLevelUp()){
int newLvl = animationList.get(animNumber[0]).getLevel();
Log.e(TAG, "level up animation playing. anim number " + animNumber[0] + ", now level " + newLvl);
// Update the lvl txt
animationList.get(animNumber[0]).getLevelTxtView().setText("LVL: " + newLvl);
// Then set the progress on this bar back to 0
animationList.get(animNumber[0]).getXpBar().setProgress(0);
}
Log.e(TAG, "anim number " + animNumber[0] + " finished, next playing " + (animNumber[0] + 1));
// Do not attempt to play the next animation if this one was the last
if (animNumber[0] < animationList.size() - 1){
animNumber[0]++;
Log.e(TAG, "playing anim number " + animNumber[0] + " to " + animationList.get(animNumber[0]).getProgressPercent() + "% full");
animationList.get(animNumber[0]).getXpBar().setProgressWithAnim(animationList.get(animNumber[0]).getProgressPercent()); // TODO: this not getting called
}
}
});
}
Log.e(TAG, "playing anim number 0");
animationList.get(0).getXpBar().setProgressWithAnim(animationList.get(0).getProgressPercent());
}
// On receiving a new skill, this tooltip will appear momentarily to notify the player
public void showNewSkillTooltip(String skillName, View view) {
Log.e(TAG, "Showing new skill tooltip. skill learned: " + skillName);
// Dynamically build a layout here, as it cannot already have a parent layout if we want to assign it to the tooltip
LinearLayout tooltipLayout = new LinearLayout(getContext());
tooltipLayout.setOrientation(LinearLayout.VERTICAL);
TextView talentTitleText = new TextView(getContext());
talentTitleText.setTextSize(18f);
talentTitleText.setText(skillName);
tooltipLayout.addView(talentTitleText);
ToolTip toolTip = new ToolTip()
.withColor(getResources().getColor(R.color.grey_300)) //or whatever you want
.withAnimationType(ToolTip.AnimationType.FROM_MASTER_VIEW)
.withContentView(tooltipLayout)
.withShadow();
tooltips.showToolTip(toolTip, view);
}
private Task<HashMap<String, Integer>> firebaseResolveCombat(String wildMonsterFirebaseId, ArrayList<String> playerMonsterIds) {
// Create the arguments to the callable function.
Map<String, Object> data = new HashMap<>();
data.put("wildMonsterId", wildMonsterFirebaseId);
data.put("playerMonsterIds", playerMonsterIds);
return mFunctions
.getHttpsCallable("resolveWildMonsterCombat")
.call(data)
.continueWith(new Continuation<HttpsCallableResult, HashMap<String, Integer>>() {
#Override
public HashMap then(#NonNull Task<HttpsCallableResult> task) throws Exception {
// This continuation runs on either success or failure, but if the task
// has failed then getResult() will throw an Exception which will be
// propagated down.
HashMap<String, Integer> result = (HashMap<String, Integer>) task.getResult().getData();
return result;
}
});
}
}
I added the whole class. Forgive the messy code.
The problem is that after the first animation which I start explicitly, it runs the onAnimationEnd function, and hits the line to run the next animation, but it never starts. The onAnimationStart function is never even called for the 2nd animation. Any ideas on why this might be?
I'll also add this takes place in a dialog fragment. Not sure if that is relevant however.
I'm working on a project, where I need to show the pitches of the words (from the song) in a GraphView. I wrote a program that would get the pitches from a song and create a .txt file from it.
Then I wrote a class, that reads the file and creates a list from it. So in the end I would have a list that consists of words and word contains pitches. In the MakePitchesList you can see what the pitches output from a song looks like. I have 0,14:23281,61 on every line. The first part of the line is timeOccurance meaning, when this pitch was heard and the second part is the pitch itself. So in this example the timeOccurance would be 0,14 and pitch at that given time 23281,61.
Here are the three classes that make a wordsList out of a pitch .txt file.
public class Pitch{
float occuranceTime;
float pitch;
public void setOccuranceTime(float occuranceTime) {
this.occuranceTime = occuranceTime;
}
public float getOccuranceTime() {
return occuranceTime;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public float getPitch() {
return pitch;
}
}
public class MakePitchesList {
String[] pitches;
List<Pitch> listOfPitches = new ArrayList<Pitch>();
public List<Pitch> getListOfPitches(){
getPitches();
for (String pitchString: pitches) {
Pitch pitch = new Pitch();
makeListOfPitches(pitch, pitchString);
}
return listOfPitches;
}
public void makeListOfPitches(Pitch pitch, String pitchString){
pitch.setPitch(getPitchesInfo(pitchString, 1));
pitch.setOccuranceTime(getPitchesInfo(pitchString, 0));
listOfPitches.add(pitch);
}
public String[] getPitches() {
pitches = pitchesRaw.split("\\r?\\n");
return pitches;
}
private float getPitchesInfo(String pitch, int position){
String[] frequencyAndTime = pitch.split("\\:");
if(position == 0){
return Float.parseFloat(frequencyAndTime[0].replace(',', '.'));
}
if(position == 1){
return Float.parseFloat(frequencyAndTime[1].replace(',', '.'));
}
else return 0;
}
String pitchesRaw =
"0,14:23281,61\n" +
"0,23:53,65\n" +
"0,37:72,53\n" +
"0,56:86,09\n" +
"0,60:88,58\n" +
"0,65:87,45\n" +
"0,70:87,11\n" +
"0,74:89,56\n" +
"0,79:96,22\n" +
"0,84:23288,24\n" +
"0,88:103,92\n" +
"0,93:107,46\n" +
"0,98:108,02\n" +
"1,02:107,51\n" +
"1,07:104,92\n" +
"1,11:105,94\n" +
"1,16:106,40\n" +
"1,21:104,43\n" +
"1,25:104,93\n" +
"1,30:108,01\n" +
"1,35:316,81\n" +
"1,39:103,98\n" +
"1,44:23297,42\n" +
"1,49:23357,42\n" +
"1,53:23359,74\n" +
"1,58:23393,04\n" +
"1,63:23244,18\n" +
"1,67:23220,51\n" +
"1,72:23250,06\n" +
"1,76:23288,84\n" +
"1,81:23241,81\n" +
"1,86:23295,22\n" +
"1,90:23268,04\n" +
"1,95:23252,78\n" +
"2,00:23224,22\n" +
"2,04:23429,71\n" +
"2,09:23214,58\n" +
"2,14:23240,70\n" +
"2,18:23237,71\n" +
"2,23:23231,22\n" +
"2,28:23222,77\n" +
"2,32:23239,73\n" +
"2,37:23235,98\n" +
"2,41:23222,16\n" +
"2,46:23224,01\n" +
"2,51:23214,26\n" +
"2,55:23223,20\n" +
"2,60:23234,11\n" +
"2,65:23221,65\n" +
"2,69:23213,45\n" +
"2,74:23217,44\n" +
"2,79:23235,93\n" +
"2,83:11122,79\n" +
"2,88:23234,58\n" +
"2,93:23229,52\n" +
"2,97:23255,48\n" +
"3,02:23254,44\n" +
"3,07:23355,41\n" +
"3,44:105,48\n" +
"3,48:115,45\n" +
"3,53:117,78\n" +
"3,58:127,36\n" +
"3,62:131,24\n" +
"3,67:130,33\n" +
"3,72:131,93\n" +
"3,76:127,32\n" +
"3,81:117,18\n" +
"3,85:117,80\n" +
"3,90:117,15\n" +
"3,95:121,04\n" +
"3,99:131,22\n" +
"4,04:130,38\n" +
"4,09:130,34\n" +
"4,13:129,57\n" +
"4,18:120,38\n" +
"4,23:121,06\n" +
"4,32:100,12\n" +
"4,37:23483,16\n" +
"4,41:112,95\n" +
"4,46:23448,04\n" +
"4,50:23396,09\n" +
"4,55:23292,90\n" +
"4,60:117,21\n" +
"4,64:116,58\n" +
"4,69:116,62\n" +
"4,74:119,18\n" +
"4,78:131,19\n" +
"4,83:130,34\n" +
"4,88:129,59\n" +
"4,92:132,64\n" +
"4,97:129,68\n" +
"5,02:132,71\n" +
"5,06:133,57\n" +
"5,11:128,94\n" +
"5,15:131,09\n" +
"5,20:132,75\n" +
"5,25:129,68\n" +
"5,29:131,26\n" +
"5,34:131,22\n" +
"5,39:130,38\n" +
"5,43:146,01\n" +
"5,48:140,43\n" +
"5,57:23450,16\n" +
"5,62:130,46\n" +
"5,67:132,02\n" +
"5,71:23243,22\n" +
"5,76:23456,28\n" +
"5,85:23246,64\n" +
"5,90:23274,97\n" +
"5,94:23310,30\n" +
"5,99:23229,71\n" +
"6,08:23214,33\n" +
"6,13:23221,53\n" +
"6,18:23263,48\n" +
"6,22:23213,17\n" +
"6,27:23235,04\n" +
"6,32:23222,02\n" +
"6,36:23214,90\n" +
"6,41:23230,05\n" +
"6,46:23212,55\n" +
"6,50:23221,33\n" +
"6,55:23226,70\n" +
"6,59:23217,07\n" +
"6,64:23272,07\n" +
"6,69:11102,74\n" +
"6,73:23263,38\n" +
"6,78:23217,53\n" +
"6,97:23243,63\n" +
"7,11:23214,11\n" +
"7,15:23229,58\n" +
"7,20:23225,70\n" +
"7,24:23244,82\n" +
"7,29:23243,09\n" +
"7,34:23249,66\n" +
"7,38:23226,67\n" +
"7,43:23246,31\n" +
"7,48:23258,55\n" +
"7,52:23230,34\n" +
"7,57:23225,60\n" +
"7,62:23280,25\n" +
"7,66:23238,08\n" +
"7,71:23221,47\n" +
"7,85:117,87\n" +
"7,89:117,19\n" +
"7,94:117,21\n" +
"7,99:117,21\n" +
"8,03:116,57\n" +
"8,08:119,10\n" +
"8,13:44,01\n" +
"8,17:129,52\n" +
"8,22:132,72\n" +
"8,27:143,19\n" +
"8,31:141,13\n" +
"8,36:139,35\n" +
"8,45:132,82\n" +
"8,50:129,76\n" +
"8,54:130,43\n" +
"8,68:94,20\n" +
"8,78:132,70\n" +
"8,82:130,43\n" +
"8,87:129,60\n" +
"8,92:130,56\n" +
"8,96:128,92\n" +
"9,01:119,19\n" +
"9,06:118,45\n" +
"9,10:103,41\n" +
"9,15:103,41\n" +
"9,20:103,89\n" +
"9,24:106,46\n" +
"9,29:214,93\n" +
"9,33:23427,95\n" +
"9,38:23356,01\n" +
"9,43:106,41\n" +
"9,47:100,57\n" +
"9,52:106,39\n" +
"9,57:104,40\n" +
"9,61:99,70\n" +
"9,66:106,42\n" +
"9,71:103,50\n" +
"9,75:104,47\n" +
"9,80:106,97\n" +
"9,85:99,68\n" +
"9,89:23454,22\n" +
"9,94:23299,56\n" +
"9,98:23275,30\n" +
"10,03:23222,72\n" +
"10,08:23246,09\n" +
"10,12:23221,14\n" +
"10,17:23240,54\n" +
"10,22:23246,81\n" +
"10,26:23224,74\n" +
"10,31:23249,41\n" +
"10,36:23214,79\n" +
"10,40:23213,46\n" +
"10,45:23259,51\n" +
"10,50:23217,39\n" +
"10,54:23215,36\n" +
"10,59:23224,87\n" +
"10,63:23242,27\n" +
"10,68:23270,82\n" +
"10,73:23243,19\n" +
"10,77:23222,75\n" +
"10,82:23268,78\n" +
"10,87:23321,62\n" +
"10,91:23259,65\n" +
"11,05:23226,24\n" +
"11,10:23222,92\n" +
"11,15:23218,83\n" +
"11,19:23211,71\n" +
"11,24:11112,28\n" +
"11,28:23261,03\n" +
"11,33:23265,31\n" +
"11,38:23245,92\n" +
"11,42:57,09\n" +
"11,61:103,45\n" +
"11,66:103,91\n" +
"11,70:102,02\n" +
"11,75:107,96\n" +
"11,80:105,43\n" +
"11,84:104,46\n" +
"11,89:116,64\n" +
"11,94:115,99\n" +
"11,98:114,77\n" +
"12,03:121,72\n" +
"12,07:123,16\n" +
"12,12:125,12\n" +
"12,17:128,85\n" +
"12,21:120,37\n" +
"12,26:116,52\n" +
"12,31:130,55\n" +
"12,35:131,06\n" +
"12,40:131,89\n" +
"12,45:128,88\n" +
"12,49:23397,75\n" +
"12,59:118,45\n" +
"12,63:116,54\n" +
"12,68:119,70\n" +
"12,72:115,45\n" +
"12,77:115,30\n" +
"12,82:119,86\n" +
"12,86:116,59\n" +
"12,91:114,13\n" +
"12,96:119,04\n" +
"13,00:118,47\n" +
"13,05:115,38\n" +
"13,10:128,92\n";
}
public class MakeWordsList {
List<Pitch> pitches;
public List<List<Pitch>> getWordsList(List<Pitch> pitchList) {
return makeWordsList(pitchList);
}
List<Pitch> oneWord = new ArrayList<>();
List<List<Pitch>> wordList = new ArrayList<>();
public List<List<Pitch>> makeWordsList(List<Pitch> pitchList){
pitches = pitchList;
int pauseCounter = 0;
for (int i = 0; i < pitchList.size(); i++) {
if(pitchList.get(i).getPitch() > 10000){
pauseCounter++;
} else {
if(pauseCounter > 0){
if(pauseCounter >= 5){
wordList.add(oneWord);
oneWord = new ArrayList<>();
}
pauseCounter = 0;
}
oneWord.add(pitchList.get(i));
}
}
if(oneWord.size() > 0){
wordList.add(oneWord);
}
return wordList;
}
}
Now from that wordsList I create a scrollable GraphView that on paper would look something like this.
Now I have set the boundaries for the GraphView: MinX = -0.8; MaxX = 0.4. This way I'm showing 1 sec at a time on screen. Then I start a thread so it would change the coordinates of the GraphView by 0.1 every 100ms, which should add up as 1 every sec.
Here it is in code:
public class MainActivity extends AppCompatActivity {
final static double GRAPH_STARTING_X = -0.8;
final static double GRAPH_ENDING_X = 0.4;
List<java.util.List<Pitch>> wordsList;
private Viewport graphViewPort;
private Handler handler;
private Runnable runnable;
private GraphView graph;
private DataPoint[] points;
private LineGraphSeries<DataPoint> series;
private int seriesNr;
private double orderNr = GRAPH_STARTING_X;
private ImageButton playRecordButton;
private List<Pitch> pitchesList;
private int songIndex;
private MediaPlayer mediaPlayer;
private boolean isPlaying = false;
#SuppressLint("ClickableViewAccessibility")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Get Textview and Button
playRecordButton = (ImageButton) findViewById(R.id.playRecord);
//Initialize pitches and words
MakePitchesList listOfPithes = new MakePitchesList();
MakeWordsList listOfWords = new MakeWordsList();
pitchesList = listOfPithes.getListOfPitches();
wordsList = listOfWords.getWordsList(pitchesList);
//Initialize graph
graph = (GraphView) findViewById(R.id.graph);
initGraph();
//ViewPort
graphViewPort = graph.getViewport();
//Handler
handler = new Handler();
playRecordButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!isPlaying){
playMedia(songIndex);
//Start moving graph
//Moved to PlayMedia - setOnPreparedListen
// drawAndMoveGraph();
isPlaying = true;
} else if(isPlaying){
//Move graph to starting position
resetGraph();
//Stop Playing audio
stopAudio();
isPlaying = false;
}
}
});
}
//Moves the graph every 100ms by 0.1 which results in 1 every second
private void drawAndMoveGraph() {
runnable = new Runnable() {
public void run() {
graphViewPort.setMinX(orderNr);
graphViewPort.setMaxX(orderNr + 1);
graph.invalidate();
if (pitchesList.size() != orderNr) {
orderNr = orderNr + 0.1;
}
handler.postDelayed(this, 100);
}
};
runnable.run();
}
//Load up and paint the graph
private void initGraph() {
for (int i = 0; i < wordsList.size(); i++) {
seriesNr = 0;
points = new DataPoint[wordsList.get(i).size()];
for (Pitch pitch : wordsList.get(i)) {
points[seriesNr] = new DataPoint(pitch.getOccuranceTime(), pitch.getPitch());
seriesNr++;
}
series = new LineGraphSeries<>(points);
series.setThickness(15);
graph.addSeries(series);
}
//VocalTestPoints - Just for vocal testing
PointsGraphSeries<DataPoint> series = new PointsGraphSeries<>(new DataPoint[]{
new DataPoint(0, 50),
new DataPoint(0, 75),
new DataPoint(0, 100),
new DataPoint(0, 125),
new DataPoint(0, 150),
new DataPoint(0, 175),
new DataPoint(0, 200),
new DataPoint(0, 225),
new DataPoint(0, 250),
new DataPoint(0, 275),
});
series.setSize(5);
series.setColor(Color.YELLOW);
graph.addSeries(series);
// set manual X bounds
graph.getViewport().setYAxisBoundsManual(true);
graph.getViewport().setMinY(-50);
graph.getViewport().setMaxY(400);
graph.getViewport().setXAxisBoundsManual(true);
graph.getViewport().setMinX(GRAPH_STARTING_X);
graph.getViewport().setMaxX(GRAPH_ENDING_X);
graph.getViewport().setScrollable(true);
}
// Play the chosen song and start moving the graph
private void playMedia(int songIndex) {
Uri uri = Uri.parse("android.resource://com.example.thermaltakei7.testsinging/" + R.raw.perfect);
mediaPlayer = new MediaPlayer();
//Reset so that the MediaPlayer is not pointing to another data source
mediaPlayer.reset();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mediaPlayer.setDataSource(getApplicationContext(), uri);
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
drawAndMoveGraph();
}
});
}
//Stop audio from playing
private void stopAudio() {
mediaPlayer.stop();
mediaPlayer.release();
}
//Reset graph to its initial position
private void resetGraph() {
handler.removeCallbacks(runnable);
graphViewPort.setMinX(GRAPH_STARTING_X);
graphViewPort.setMaxX(GRAPH_ENDING_X);
graph.invalidate();
orderNr = GRAPH_STARTING_X;
}
}
To sum up what the code does: it gets the wordsList from the pitches, initializes the graph, starts listening button click. When button is clicked, it starts moving graph as described above and plays audio.
The problem is that the audio and moving graphView go out of sync. For the first 10sec or so it works like it should but then the lines start falling behind. I would really appreciate if someone took their time to understand all of this mess.
Maybe picture of the actual final graphView will also help. .
Blue line is the first incoming word.
The audio's timing and line should meet at "0".
I have also created a small sample that only contains the classes and topics mentioned above.
It can be accessed from here:
https://github.com/richardboss/testSinging
This code containst a song in its raw folder so it's a bit bigger than it should be, but this was the fastest way to show a demo of what I was trying to say.
Any ideas, suggestions, help is really welcome. Or comments on the code style.
this is my method to implement number of textview as per the word in sentence. each word have click event or touch event.
static ArrayList<TextView> sentence(String[] arr,
LinearLayout linelay_wordIn1) {
if (linelay_wordIn1.getChildCount() > 0)
linelay_wordIn1.removeAllViews();
if (allTextView != null) {
allTextView.remove(txt);
allTextView.clear();
System.out.println("hello remove all textview here");
} else {
System.out.println("hello all textview array is null here");
}
String str1 = "";
for (int i = 0; i < arr.length; i++) {
str1 = str1 + arr[i].toString();
System.out.println(" senctence separte in word " + arr[i]
+ " words" + arr.length);
}
/* listview for getting textview */
System.out.println("sentence " + str1.toString() + "str1 length :: "
+ str1.length());
txt = new TextView[arr.length];
LinearLayout.LayoutParams lp;
lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER;
// lp.setMarginStart(arr.length);
for (int j = 0; j < arr.length; j++) {
txt[j] = new TextView(contextG);
txt[j].setId(j);
txt[j].setBackgroundResource(Color.TRANSPARENT);
txt[j].setTextSize(60);
txt[j].setTypeface(
Typeface.createFromAsset(contextG.getAssets(), "TIMES.TTF"),
Typeface.BOLD);
txt[j].setText(arr[j]);
txt[j].setLayoutParams(lp);
txt[j].setTextColor(Color.BLACK);
txt[j].setOnTouchListener(myListner);
System.out.println("txt[j]" + j + "id " + txt[j].getId());
allTextView.add(txt[j]); /* add textview into arraylist */
linelay_wordIn1.addView(txt[j], j);
}
return allTextView;
}
my touch Listener code is here
public static OnTouchListener myListner = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
TextView tv = (TextView) v;
//Layout layout = ((TextView) v).getLayout();
String str = tv.getText().toString();
int x = (int) event.getX();
int y = (int) event.getY();
//if (layout != null) {
//int line = layout.getLineForVertical(y);
//int characterOffset = layout.getOffsetForHorizontal(line, x);
//Log.i("index", "" + characterOffset);
//}
System.out.println(" str of sentence :: " + str.toString());
if (Music.playing()) {
// do nothing
} else {
try {
/* Music renew for memory leak problems */
Music.renewMediaPlayer();
/*--------------------*/
if (str.equalsIgnoreCase("I ")) {
Music.play(contextG, sent_audio1[0]);
} else if (str.equals("like ")) {
Music.play(contextG, sent_audio1[1]);
} else if (str.equals("to ")) {
Music.play(contextG, sent_audio1[2]);
} else if (str.equals("ski.")) {
Music.play(contextG, sent_audio1[3]);
tooltip(tool1, 0, 3, x, y);// tool1 is for guide text
// array
// and 0
// for index
} else if (str.equals("When ")) {
Music.play(contextG, sent_audio2[0]);
tooltip(tool2, 0, 0, x, y);
} else if (str.equals("the ")) {
Music.play(contextG, sent_audio2[1]);
tooltip(tool2, 0, 1, x, y);
} else if (str.equals("snow ")) {
Music.play(contextG, sent_audio2[2]);
tooltip(tool2, 0, 2, x, y);
} else if (str.equals("falls, ")) {
Music.play(contextG, sent_audio2[3]);
} else if (str.equals("head ")) {
Music.play(contextG, sent_audio2[5]);
tooltip(tool2, 1, 5, x, y);
} else if (str.equals("slopes.")) {
float x1 = event.getX();
float y1 = event.getY();
Music.play(contextG, sent_audio2[8]);
tooltip(tool2, 2, 8, x1, y1);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
return true;
}
};
here : number words store in string array to set as text in textviews
/* sentence here */
static String[] words1 = { "I ", "like ", "to ", "ski." };
static String[] words2 = { "When ", "the ", "snow ", "falls, ", "I ",
"head ", "to ", "the ", "slopes." };
static String[] words3 = { "My ", "Children ", "join ", "me ", "on ",
"the ", "trip ", "to ", "the ", "hills. " };
static String[] words4 = { "We ", "ride ", "in ", "cable ", "cars ", "to ",
"the ", "top ", "of ", "the ", "hill. " };
static String[] words5 = { "The ", "snow ", "sparkles ", "in ", "the ",
"sunshine ", "as", "we ", "ski ", "the ", "trails. " };
static String[] words6 = { "I ", "pass ", "many ", "novice ", "skiers ",
"as ", "they ", "struggle ", "to ", "stay ", "up. " };
static String[] words7 = { "I ", "try ", "to ", "watch ", "out ", "as ",
"I ", "whiz ", "by. " };
static String[] words8 = { "If ", "I ", "do ", "not, ", "I ", "will ",
"fall ", "too. " };
here is : one method to show another toast on textview array x and y position on each and every click.
static void tooltip(String[] arr, int toolstringindex, int placeindex,
float x, float y) {
FrameLayout layout = new FrameLayout(contextG);
layout.setBackgroundResource(R.drawable.tool_tip_img);
TextView tv = new TextView(contextG);
// set the TextView properties like color, size etc
tv.setTextColor(Color.BLACK);
tv.setTextSize(40);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.TOP);
lp.leftMargin = 10;
tv.setLayoutParams(lp);
// set the text you want to show in Toast
tv.setText(arr[toolstringindex]);
layout.addView(tv);
// // int[] values = new int[2];
float viewx = allTextView.get(placeindex).getX();
float viewy = allTextView.get(placeindex).getY();
// // int x = values[0];
// // int y = values[1];
System.out.println(" hello x::" + x + 50 + "y :: " + y);
Toast toast = new Toast(contextG);
toast.setGravity(Gravity.CENTER_VERTICAL, (int) x, (int) y - 80);
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
}
now actual problem is that x and y position not coming perfectly .its
showing somewhere else on screen so, i didnt get it what to do? if any
one have better idea for this please let me to implement this . some
line of code will be helpful to me.please help me. Thanks in advance
Change this:
LinearLayout.LayoutParams lp;
lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER;
to this:
LinearLayout.LayoutParams lp;
lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.NO_GRAVITY;
lp.setMargins(x, y, maxX, maxY);
the maxX and maxY are irrelevant just set them to text width and height.